/*
 * Copyright (c) 2024 Shenzhen Kaihong Digital Industry Development Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { outgoingHttpHeaders, requestOptions, incomingHttpHeaders,
  secureServerOptions, serverHttp2Stream, http2Server, http2SecureServer,serverHttp2Session,
  clientHttp2Session, secureClientSessionOptions,
  ClientSessionOptions,
  Socket,
  SessionState,
  IncomingHttpStatusHeader,
  ClientSessionRequestOptions,
  Error
} from '../typeDeclaration';
import { ServerOptions } from '../server'
import http from '@ohos.net.http';
import socket from '@ohos.net.socket';
import { URL } from './url';
import { TLSSocket } from './tls';
import { BufferClass, Buffer } from './buffer';
import hilog from '@ohos.hilog';
import Url from '@ohos.url'
import { BusinessError } from '@ohos.base';
import { stream  } from '@kit.ArkTS';
import {
  MAX_ADDITIONAL_SETTINGS
} from './utils'
import { parse } from 'protobufjs';
import LogUtils from '../../ets/http2/LogUtils';

export type RequestOptions = requestOptions;
export type OutgoingHttpHeaders = outgoingHttpHeaders;
export type IncomingHttpHeaders = incomingHttpHeaders;
// export type ServerHttp2Stream = serverHttp2Stream;
// export type ClientHttp2Stream = clientHttp2Stream;
export type Http2Server = http2Server;
export type Http2SecureServer = http2SecureServer;
// export type ServerHttp2Session = serverHttp2Session
export type SecureServerOptions = secureServerOptions
// export type Http2Session = http2Session
// export type ClientHttp2Session = clientHttp2Session
export type SecureClientSessionOptions = secureClientSessionOptions
export interface ServerStreamResponseOptions {
  endStream?: boolean | undefined;
  waitForTrailers?: boolean | undefined;
}

enum ERR_CODE {
  ERR_HTTP2_ALTSVC_INVALID_ORIGIN,
  ERR_HTTP2_ALTSVC_LENGTH,
  ERR_HTTP2_CONNECT_AUTHORITY,
  ERR_HTTP2_CONNECT_PATH,
  ERR_HTTP2_CONNECT_SCHEME,
  ERR_HTTP2_GOAWAY_SESSION,
  ERR_HTTP2_HEADERS_AFTER_RESPOND,
  ERR_HTTP2_HEADERS_SENT,
  ERR_HTTP2_INVALID_INFO_STATUS,
  ERR_HTTP2_INVALID_ORIGIN,
  ERR_HTTP2_INVALID_PACKED_SETTINGS_LENGTH,
  ERR_HTTP2_INVALID_SESSION,
  ERR_HTTP2_INVALID_SETTING_VALUE,
  ERR_HTTP2_INVALID_STREAM,
  ERR_HTTP2_MAX_PENDING_SETTINGS_ACK,
  ERR_HTTP2_NESTED_PUSH,
  ERR_HTTP2_NO_MEM,
  ERR_HTTP2_NO_SOCKET_MANIPULATION,
  ERR_HTTP2_ORIGIN_LENGTH,
  ERR_HTTP2_OUT_OF_STREAMS,
  ERR_HTTP2_PAYLOAD_FORBIDDEN,
  ERR_HTTP2_PING_CANCEL,
  ERR_HTTP2_PING_LENGTH,
  ERR_HTTP2_PUSH_DISABLED,
  ERR_HTTP2_SEND_FILE,
  ERR_HTTP2_SEND_FILE_NOSEEK,
  ERR_HTTP2_SESSION_ERROR,
  ERR_HTTP2_SETTINGS_CANCEL,
  ERR_HTTP2_SOCKET_BOUND,
  ERR_HTTP2_SOCKET_UNBOUND,
  ERR_HTTP2_STATUS_101,
  ERR_HTTP2_STATUS_INVALID,
  ERR_HTTP2_STREAM_CANCEL,
  ERR_HTTP2_STREAM_ERROR,
  ERR_HTTP2_STREAM_SELF_DEPENDENCY,
  ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS,
  ERR_HTTP2_TRAILERS_ALREADY_SENT,
  ERR_HTTP2_TRAILERS_NOT_READY,
  ERR_HTTP2_UNSUPPORTED_PROTOCOL,
  ERR_INVALID_ARG_TYPE,
  ERR_INVALID_ARG_VALUE,
  ERR_INVALID_CHAR,
  ERR_INVALID_HTTP_TOKEN,
  ERR_OUT_OF_RANGE,
  ERR_SOCKET_CLOSED,
}

export function createSecureServer(secureServerOptions:secureServerOptions):any {
  console.log('Mocking createSecureServer with options:', secureServerOptions);
  const mockServer = {
    listen: (port, callback) => {
      console.log(`Secure server listening on port ${port}`);
      if (callback) callback();
    },
    close: () => {
      console.log('Secure server closed');
    }
  };
  return mockServer;
};

export function createServer(commonServerOptions:ServerOptions): any {
  const mockRequest = {
    once: function (event: string, listener: Function) {
    },
    removeAllListeners: function (event?: string) {
    },

    end: function () {
    },

    listen:function (){

    }
  }
}

export const constants = {
  NGHTTP2_NO_ERROR:0,
  NGHTTP2_REFUSED_STREAM:7,
  NGHTTP2_CANCEL:8,
  NGHTTP2_ENHANCE_YOUR_CALM:11,
  NGHTTP2_INADEQUATE_SECURITY:12,
  NGHTTP2_INTERNAL_ERROR:2,
  HTTP2_HEADER_ACCEPT_ENCODING:'accept-encoding',
  HTTP2_HEADER_TE:'te',
  HTTP2_HEADER_CONTENT_TYPE:'content-type',
  HTTP_STATUS_OK:http.ResponseCode.OK,
  NGHTTP2_FLAG_END_STREAM:1,
  HTTP2_HEADER_AUTHORITY:'authority',
  HTTP2_HEADER_METHOD:"method",
  HTTP2_HEADER_USER_AGENT:"user-agent",
  HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE:415,
  HTTP2_HEADER_PATH:":path",
  HTTP2_HEADER_STATUS:":status"
}

const expectedHeaderNames = {
  HTTP2_HEADER_STATUS: ':status',
  HTTP2_HEADER_METHOD: ':method',
  HTTP2_HEADER_AUTHORITY: ':authority',
  HTTP2_HEADER_SCHEME: ':scheme',
  HTTP2_HEADER_PATH: ':path',
  HTTP2_HEADER_PROTOCOL: ':protocol',
  HTTP2_HEADER_DATE: 'date',
  HTTP2_HEADER_ACCEPT_CHARSET: 'accept-charset',
  HTTP2_HEADER_ACCEPT_ENCODING: 'accept-encoding',
  HTTP2_HEADER_ACCEPT_LANGUAGE: 'accept-language',
  HTTP2_HEADER_ACCEPT_RANGES: 'accept-ranges',
  HTTP2_HEADER_ACCEPT: 'accept',
  HTTP2_HEADER_ACCESS_CONTROL_ALLOW_CREDENTIALS: 'access-control-allow-credentials',
  HTTP2_HEADER_ACCESS_CONTROL_ALLOW_HEADERS: 'access-control-allow-headers',
  HTTP2_HEADER_ACCESS_CONTROL_ALLOW_METHODS: 'access-control-allow-methods',
  HTTP2_HEADER_ACCESS_CONTROL_ALLOW_ORIGIN: 'access-control-allow-origin',
  HTTP2_HEADER_ACCESS_CONTROL_EXPOSE_HEADERS: 'access-control-expose-headers',
  HTTP2_HEADER_ACCESS_CONTROL_MAX_AGE: 'access-control-max-age',
  HTTP2_HEADER_ACCESS_CONTROL_REQUEST_HEADERS: 'access-control-request-headers',
  HTTP2_HEADER_ACCESS_CONTROL_REQUEST_METHOD: 'access-control-request-method',
  HTTP2_HEADER_AGE: 'age',
  HTTP2_HEADER_ALLOW: 'allow',
  HTTP2_HEADER_AUTHORIZATION: 'authorization',
  HTTP2_HEADER_CACHE_CONTROL: 'cache-control',
  HTTP2_HEADER_CONTENT_DISPOSITION: 'content-disposition',
  HTTP2_HEADER_CONTENT_ENCODING: 'content-encoding',
  HTTP2_HEADER_CONTENT_LANGUAGE: 'content-language',
  HTTP2_HEADER_CONTENT_LENGTH: 'content-length',
  HTTP2_HEADER_CONTENT_LOCATION: 'content-location',
  HTTP2_HEADER_CONTENT_RANGE: 'content-range',
  HTTP2_HEADER_CONTENT_TYPE: 'content-type',
  HTTP2_HEADER_COOKIE: 'cookie',
  HTTP2_HEADER_CONNECTION: 'connection',
  HTTP2_HEADER_DNT: 'dnt',
  HTTP2_HEADER_ETAG: 'etag',
  HTTP2_HEADER_EXPECT: 'expect',
  HTTP2_HEADER_EXPIRES: 'expires',
  HTTP2_HEADER_FORWARDED: 'forwarded',
  HTTP2_HEADER_FROM: 'from',
  HTTP2_HEADER_HOST: 'host',
  HTTP2_HEADER_IF_MATCH: 'if-match',
  HTTP2_HEADER_IF_MODIFIED_SINCE: 'if-modified-since',
  HTTP2_HEADER_IF_NONE_MATCH: 'if-none-match',
  HTTP2_HEADER_IF_RANGE: 'if-range',
  HTTP2_HEADER_IF_UNMODIFIED_SINCE: 'if-unmodified-since',
  HTTP2_HEADER_LAST_MODIFIED: 'last-modified',
  HTTP2_HEADER_LINK: 'link',
  HTTP2_HEADER_LOCATION: 'location',
  HTTP2_HEADER_MAX_FORWARDS: 'max-forwards',
  HTTP2_HEADER_PREFER: 'prefer',
  HTTP2_HEADER_PROXY_AUTHENTICATE: 'proxy-authenticate',
  HTTP2_HEADER_PROXY_AUTHORIZATION: 'proxy-authorization',
  HTTP2_HEADER_PROXY_CONNECTION: 'proxy-connection',
  HTTP2_HEADER_RANGE: 'range',
  HTTP2_HEADER_REFERER: 'referer',
  HTTP2_HEADER_REFRESH: 'refresh',
  HTTP2_HEADER_RETRY_AFTER: 'retry-after',
  HTTP2_HEADER_SERVER: 'server',
  HTTP2_HEADER_SET_COOKIE: 'set-cookie',
  HTTP2_HEADER_STRICT_TRANSPORT_SECURITY: 'strict-transport-security',
  HTTP2_HEADER_TRAILER: 'trailer',
  HTTP2_HEADER_TRANSFER_ENCODING: 'transfer-encoding',
  HTTP2_HEADER_TK: 'tk',
  HTTP2_HEADER_UPGRADE_INSECURE_REQUESTS: 'upgrade-insecure-requests',
  HTTP2_HEADER_USER_AGENT: 'user-agent',
  HTTP2_HEADER_VARY: 'vary',
  HTTP2_HEADER_VIA: 'via',
  HTTP2_HEADER_WARNING: 'warning',
  HTTP2_HEADER_WWW_AUTHENTICATE: 'www-authenticate',
  HTTP2_HEADER_X_CONTENT_TYPE_OPTIONS: 'x-content-type-options',
  HTTP2_HEADER_X_FRAME_OPTIONS: 'x-frame-options',
  HTTP2_HEADER_KEEP_ALIVE: 'keep-alive',
  HTTP2_HEADER_CONTENT_MD5: 'content-md5',
  HTTP2_HEADER_TE: 'te',
  HTTP2_HEADER_UPGRADE: 'upgrade',
  HTTP2_HEADER_HTTP2_SETTINGS: 'http2-settings',
  HTTP2_HEADER_X_XSS_PROTECTION: 'x-xss-protection',
  HTTP2_HEADER_ALT_SVC: 'alt-svc',
  HTTP2_HEADER_CONTENT_SECURITY_POLICY: 'content-security-policy',
  HTTP2_HEADER_EARLY_DATA: 'early-data',
  HTTP2_HEADER_EXPECT_CT: 'expect-ct',
  HTTP2_HEADER_ORIGIN: 'origin',
  HTTP2_HEADER_PURPOSE: 'purpose',
  HTTP2_HEADER_TIMING_ALLOW_ORIGIN: 'timing-allow-origin',
  HTTP2_HEADER_X_FORWARDED_FOR: 'x-forwarded-for',
  HTTP2_HEADER_PRIORITY: 'priority',
};

const expectedNGConstants = {
  NGHTTP2_SESSION_SERVER: 0,
  NGHTTP2_SESSION_CLIENT: 1,
  NGHTTP2_STREAM_STATE_IDLE: 1,
  NGHTTP2_STREAM_STATE_OPEN: 2,
  NGHTTP2_STREAM_STATE_RESERVED_LOCAL: 3,
  NGHTTP2_STREAM_STATE_RESERVED_REMOTE: 4,
  NGHTTP2_STREAM_STATE_HALF_CLOSED_LOCAL: 5,
  NGHTTP2_STREAM_STATE_HALF_CLOSED_REMOTE: 6,
  NGHTTP2_STREAM_STATE_CLOSED: 7,
  NGHTTP2_HCAT_REQUEST: 0,
  NGHTTP2_HCAT_RESPONSE: 1,
  NGHTTP2_HCAT_PUSH_RESPONSE: 2,
  NGHTTP2_HCAT_HEADERS: 3,
  NGHTTP2_NO_ERROR: 0,
  NGHTTP2_PROTOCOL_ERROR: 1,
  NGHTTP2_INTERNAL_ERROR: 2,
  NGHTTP2_FLOW_CONTROL_ERROR: 3,
  NGHTTP2_SETTINGS_TIMEOUT: 4,
  NGHTTP2_STREAM_CLOSED: 5,
  NGHTTP2_FRAME_SIZE_ERROR: 6,
  NGHTTP2_REFUSED_STREAM: 7,
  NGHTTP2_CANCEL: 8,
  NGHTTP2_COMPRESSION_ERROR: 9,
  NGHTTP2_CONNECT_ERROR: 10,
  NGHTTP2_ENHANCE_YOUR_CALM: 11,
  NGHTTP2_INADEQUATE_SECURITY: 12,
  NGHTTP2_HTTP_1_1_REQUIRED: 13,
  NGHTTP2_NV_FLAG_NONE: 0,
  NGHTTP2_NV_FLAG_NO_INDEX: 1,
  NGHTTP2_ERR_DEFERRED: -508,
  NGHTTP2_ERR_NOMEM: -901,
  NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE: -509,
  NGHTTP2_ERR_INVALID_ARGUMENT: -501,
  NGHTTP2_ERR_STREAM_CLOSED: -510,
  NGHTTP2_ERR_FRAME_SIZE_ERROR: -522,
  NGHTTP2_FLAG_NONE: 0,
  NGHTTP2_FLAG_END_STREAM: 1,
  NGHTTP2_FLAG_END_HEADERS: 4,
  NGHTTP2_FLAG_ACK: 1,
  NGHTTP2_FLAG_PADDED: 8,
  NGHTTP2_FLAG_PRIORITY: 32,
  NGHTTP2_DEFAULT_WEIGHT: 16,
  NGHTTP2_SETTINGS_HEADER_TABLE_SIZE: 1,
  NGHTTP2_SETTINGS_ENABLE_PUSH: 2,
  NGHTTP2_SETTINGS_MAX_CONCURRENT_STREAMS: 3,
  NGHTTP2_SETTINGS_INITIAL_WINDOW_SIZE: 4,
  NGHTTP2_SETTINGS_MAX_FRAME_SIZE: 5,
  NGHTTP2_SETTINGS_MAX_HEADER_LIST_SIZE: 6,
  NGHTTP2_SETTINGS_ENABLE_CONNECT_PROTOCOL: 8
};

const expectedStatusCodes = {
  HTTP_STATUS_CONTINUE: 100,
  HTTP_STATUS_SWITCHING_PROTOCOLS: 101,
  HTTP_STATUS_PROCESSING: 102,
  HTTP_STATUS_EARLY_HINTS: 103,
  HTTP_STATUS_OK: 200,
  HTTP_STATUS_CREATED: 201,
  HTTP_STATUS_ACCEPTED: 202,
  HTTP_STATUS_NON_AUTHORITATIVE_INFORMATION: 203,
  HTTP_STATUS_NO_CONTENT: 204,
  HTTP_STATUS_RESET_CONTENT: 205,
  HTTP_STATUS_PARTIAL_CONTENT: 206,
  HTTP_STATUS_MULTI_STATUS: 207,
  HTTP_STATUS_ALREADY_REPORTED: 208,
  HTTP_STATUS_IM_USED: 226,
  HTTP_STATUS_MULTIPLE_CHOICES: 300,
  HTTP_STATUS_MOVED_PERMANENTLY: 301,
  HTTP_STATUS_FOUND: 302,
  HTTP_STATUS_SEE_OTHER: 303,
  HTTP_STATUS_NOT_MODIFIED: 304,
  HTTP_STATUS_USE_PROXY: 305,
  HTTP_STATUS_TEMPORARY_REDIRECT: 307,
  HTTP_STATUS_PERMANENT_REDIRECT: 308,
  HTTP_STATUS_BAD_REQUEST: 400,
  HTTP_STATUS_UNAUTHORIZED: 401,
  HTTP_STATUS_PAYMENT_REQUIRED: 402,
  HTTP_STATUS_FORBIDDEN: 403,
  HTTP_STATUS_NOT_FOUND: 404,
  HTTP_STATUS_METHOD_NOT_ALLOWED: 405,
  HTTP_STATUS_NOT_ACCEPTABLE: 406,
  HTTP_STATUS_PROXY_AUTHENTICATION_REQUIRED: 407,
  HTTP_STATUS_REQUEST_TIMEOUT: 408,
  HTTP_STATUS_CONFLICT: 409,
  HTTP_STATUS_GONE: 410,
  HTTP_STATUS_LENGTH_REQUIRED: 411,
  HTTP_STATUS_PRECONDITION_FAILED: 412,
  HTTP_STATUS_PAYLOAD_TOO_LARGE: 413,
  HTTP_STATUS_URI_TOO_LONG: 414,
  HTTP_STATUS_UNSUPPORTED_MEDIA_TYPE: 415,
  HTTP_STATUS_RANGE_NOT_SATISFIABLE: 416,
  HTTP_STATUS_EXPECTATION_FAILED: 417,
  HTTP_STATUS_TEAPOT: 418,
  HTTP_STATUS_MISDIRECTED_REQUEST: 421,
  HTTP_STATUS_UNPROCESSABLE_ENTITY: 422,
  HTTP_STATUS_LOCKED: 423,
  HTTP_STATUS_FAILED_DEPENDENCY: 424,
  HTTP_STATUS_TOO_EARLY: 425,
  HTTP_STATUS_UPGRADE_REQUIRED: 426,
  HTTP_STATUS_PRECONDITION_REQUIRED: 428,
  HTTP_STATUS_TOO_MANY_REQUESTS: 429,
  HTTP_STATUS_REQUEST_HEADER_FIELDS_TOO_LARGE: 431,
  HTTP_STATUS_UNAVAILABLE_FOR_LEGAL_REASONS: 451,
  HTTP_STATUS_INTERNAL_SERVER_ERROR: 500,
  HTTP_STATUS_NOT_IMPLEMENTED: 501,
  HTTP_STATUS_BAD_GATEWAY: 502,
  HTTP_STATUS_SERVICE_UNAVAILABLE: 503,
  HTTP_STATUS_GATEWAY_TIMEOUT: 504,
  HTTP_STATUS_HTTP_VERSION_NOT_SUPPORTED: 505,
  HTTP_STATUS_VARIANT_ALSO_NEGOTIATES: 506,
  HTTP_STATUS_INSUFFICIENT_STORAGE: 507,
  HTTP_STATUS_LOOP_DETECTED: 508,
  HTTP_STATUS_BANDWIDTH_LIMIT_EXCEEDED: 509,
  HTTP_STATUS_NOT_EXTENDED: 510,
  HTTP_STATUS_NETWORK_AUTHENTICATION_REQUIRED: 511
};

const proxySocketHandler = {
  get(session, prop) {
    switch (prop) {
      case 'setTimeout':
      case 'ref':
      case 'unref':
        return session[prop].bind(session);
      case 'destroy':
      case 'emit':
      case 'end':
      case 'pause':
      case 'read':
      case 'resume':
      case 'write':
      case 'setEncoding':
      case 'setKeepAlive':
      case 'setNoDelay': {
        hilog.error(0x8691, 'ohos_grpc_node', 'ERR_HTTP2_NO_SOCKET_MANIPULATION');
        throw new Error('ERR_HTTP2_NO_SOCKET_MANIPULATION');
      }
      default: {
        const socket = session[kSocket];
        if (socket === undefined) {
          hilog.error(0x8691, 'ohos_grpc_node', 'ERR_HTTP2_SOCKET_UNBOUND');
          throw new Error('ERR_HTTP2_SOCKET_UNBOUND');
        }
        const value = socket[prop];
        return typeof value === 'function' ? value.bind(socket) : value;
      }
    }
  },
  getPrototypeOf(session) {
    const socket = session[kSocket];
    if (socket === undefined) {
      hilog.error(0x8691, 'ohos_grpc_node', 'ERR_HTTP2_SOCKET_UNBOUND');
      throw new Error('ERR_HTTP2_SOCKET_UNBOUND');
    }
    return socket;
  },
  set(session, prop, value) {
    switch (prop) {
      case 'setTimeout':
      case 'ref':
      case 'unref':
        session[prop] = value;
        return true;
      case 'destroy':
      case 'emit':
      case 'end':
      case 'pause':
      case 'read':
      case 'resume':
      case 'write':
      case 'setEncoding':
      case 'setKeepAlive':
      case 'setNoDelay': {
        hilog.error(0x8691, 'ohos_grpc_node', 'ERR_HTTP2_NO_SOCKET_MANIPULATION');
        throw new Error('ERR_HTTP2_NO_SOCKET_MANIPULATION');
      }
      default: {
        const socket = session[kSocket];
        if (socket === undefined) {
          hilog.error(0x8691, 'ohos_grpc_node', 'ERR_HTTP2_SOCKET_UNBOUND');
          throw new Error('ERR_HTTP2_SOCKET_UNBOUND');
        }

        socket[prop] = value;
        return true;
      }
    }
  },
};

const STREAM_FLAGS_PENDING = 0x0;
const STREAM_FLAGS_READY = 0x1;
const STREAM_FLAGS_CLOSED = 0x2;
const STREAM_FLAGS_HEADERS_SENT = 0x4;
const STREAM_FLAGS_HEAD_REQUEST = 0x8;
const STREAM_FLAGS_ABORTED = 0x10;
const STREAM_FLAGS_HAS_TRAILERS = 0x20;

const SESSION_FLAGS_PENDING = 0x0;
const SESSION_FLAGS_READY = 0x1;
const SESSION_FLAGS_CLOSED = 0x2;
const SESSION_FLAGS_DESTROYED = 0x4;

const kMaxFrameSize = (2 ** 24) - 1;
const kMaxInt = (2 ** 32) - 1;
const kMaxStreams = (2 ** 32) - 1;
const kMaxALTSVC = (2 ** 14) - 2;

export interface Settings {
  headerTableSize?: number | undefined;
  enablePush?: boolean | undefined;
  initialWindowSize?: number | undefined;
  maxFrameSize?: number | undefined;
  maxConcurrentStreams?: number | undefined;
  maxHeaderListSize?: number | undefined;
  enableConnectProtocol?: boolean | undefined;
}

// export class myhttp2Session implements ClientHttp2Session {
//   readonly alpnProtocol?: string | undefined;
//   readonly closed: boolean;
//   readonly connecting: boolean;
//   readonly destroyed: boolean;
//   readonly encrypted?: boolean | undefined;
//   readonly localSettings: Settings;
//   readonly originSet?: string[] | undefined;
//   readonly pendingSettingsAck: boolean;
//   readonly remoteSettings: Settings;
//   readonly socket: socket.TLSSocket ;
//   readonly state: SessionState;
//   readonly type: number;
//
//   constructor() {
//   }
//
//   close(callback?: () => void): void {};
//   destroy(error?: Error, code?: number): void{};
//   goaway(code?: number, lastStreamID?: number, opaqueData?: ArrayBufferView): void {
//     return;
//   };
//   ping(callback: (err: Error | null, duration: number, payload: BufferClass) => void): boolean {
//     return true;
//   };
//   // ping(payload: ArrayBufferView, callback: (err: Error | null, duration: number, payload: Buffer) => void): boolean {
//   //   return true;
//   // };
//
//   ref(): void {};
//   setLocalWindowSize(windowSize: number): void {};
//   setTimeout(msecs: number, callback?: () => void): void {};
//   settings(
//     settings: Settings,
//     callback?: (err: Error | null, settings: Settings, duration: number) => void,
//   ): void {};
//   unref(): void {};
//
//
//   removeAllListeners() {};
//
//   request(headers?: outgoingHttpHeaders, options?: ClientSessionRequestOptions): clientHttp2Stream {
//     return null;
//   };
//   addListener(event: "altsvc", listener: (alt: string, origin: string, stream: number) => void): this;
//   addListener(event: "origin", listener: (origins: string[]) => void): this;
//   addListener(
//     event: "connect",
//     listener: (session: clientHttp2Session, socket: socket.TLSSocket) => void,
//   ): this;
//   addListener(
//     event: "stream",
//     listener: (
//       stream: clientHttp2Stream,
//       headers: incomingHttpHeaders & IncomingHttpStatusHeader,
//       flags: number,
//     ) => void,
//   ): this;
//   addListener(event: string | symbol, listener: (...args: any[]) => void): this {
//     return this;
//   };;
//   emit(event: "altsvc", alt: string, origin: string, stream: number): boolean;
//   emit(event: "origin", origins: readonly string[]): boolean;
//   emit(event: "connect", session: clientHttp2Session, socket: socket.TLSSocket): boolean;
//   emit(
//     event: "stream",
//     stream: clientHttp2Stream,
//     headers: incomingHttpHeaders & IncomingHttpStatusHeader,
//     flags: number,
//   ): boolean;
//   emit(event: string | symbol, ...args: any[]): boolean{
//     return false;
//   };;
//   on(event: "altsvc", listener: (alt: string, origin: string, stream: number) => void): this;
//   on(event: "origin", listener: (origins: string[]) => void): this;
//   on(event: "connect", listener: (session: clientHttp2Session, socket: socket.TLSSocket) => void): this;
//   on(
//     event: "stream",
//     listener: (
//       stream: clientHttp2Stream,
//       headers: incomingHttpHeaders & IncomingHttpStatusHeader,
//       flags: number,
//     ) => void,
//   ): this;
//   on(event: string | symbol, listener: (...args: any[]) => void): this {
//     return this;
//   };
//   once(event: "altsvc", listener: (alt: string, origin: string, stream: number) => void): this;
//   once(event: "origin", listener: (origins: string[]) => void): this;
//   once(
//     event: "connect",
//     listener: (session: clientHttp2Session, socket: socket.TLSSocket) => void,
//   ): this;
//   once(
//     event: "stream",
//     listener: (
//       stream: clientHttp2Stream,
//       headers: incomingHttpHeaders & IncomingHttpStatusHeader,
//       flags: number,
//     ) => void,
//   ): this;
//   once(event: string | symbol, listener: (...args: any[]) => void): this {
//     return this;
//   };
//   prependListener(event: "altsvc", listener: (alt: string, origin: string, stream: number) => void): this;
//   prependListener(event: "origin", listener: (origins: string[]) => void): this;
//   prependListener(
//     event: "connect",
//     listener: (session: clientHttp2Session, socket: socket.TLSSocket) => void,
//   ): this;
//   prependListener(
//     event: "stream",
//     listener: (
//       stream: clientHttp2Stream,
//       headers: incomingHttpHeaders & IncomingHttpStatusHeader,
//       flags: number,
//     ) => void,
//   ): this;
//   prependListener(event: string | symbol, listener: (...args: any[]) => void): this {
//     return this;
//   };
//   prependOnceListener(event: "altsvc", listener: (alt: string, origin: string, stream: number) => void): this;
//   prependOnceListener(event: "origin", listener: (origins: string[]) => void): this;
//   prependOnceListener(
//     event: "connect",
//     listener: (session: clientHttp2Session, socket: socket.TLSSocket) => void,
//   ): this;
//   prependOnceListener(
//     event: "stream",
//     listener: (
//       stream: clientHttp2Stream,
//       headers: incomingHttpHeaders & IncomingHttpStatusHeader,
//       flags: number,
//     ) => void,
//   ): this;
//   prependOnceListener(event: string | symbol, listener: (...args: any[]) => void): this {
//     return this;
//   };
// }

const kAlpnProtocol = Symbol('alpnProtocol');
const kAuthority = Symbol('authority');
const kEncrypted = Symbol('encrypted');
const kID = Symbol('id');
const kInit = Symbol('init');
const kInfoHeaders = Symbol('sent-info-headers');
const kLocalSettings = Symbol('local-settings');
const kNativeFields = Symbol('kNativeFields');
const kOptions = Symbol('options');
// const kOwner = owner_symbol;
const kOrigin = Symbol('origin');
const kPendingRequestCalls = Symbol('kPendingRequestCalls');
const kProceed = Symbol('proceed');
const kProtocol = Symbol('protocol');
const kRemoteSettings = Symbol('remote-settings');
const kRequestAsyncResource = Symbol('requestAsyncResource');
const kSelectPadding = Symbol('select-padding');
const kSentHeaders = Symbol('sent-headers');
const kSentTrailers = Symbol('sent-trailers');
const kServer = Symbol('server');
const kState = Symbol('state');
const kType = Symbol('type');
const kWriteGeneric = Symbol('write-generic');
const kBoundSession = Symbol('kBoundSession');
const kSession = Symbol('kSession');
const kMaybeDestroy = Symbol('kMaybeDestroy');
const kSensitiveHeaders = Symbol('sensitiveHeaders');
const kSocket = Symbol('socket');
const kProxySocket = Symbol('proxySocket');
const kRequest = Symbol('request');
const kHandle = Symbol('kHandle');
const kTimeout = Symbol('kTimeout');

const kUpdateTimer = Symbol('kUpdateTimer');
const kAfterAsyncWrite = Symbol('kAfterAsyncWrite');

enum SessionBitfieldFlags {
  kSessionHasRemoteSettingsListeners,
  kSessionRemoteSettingsIsUpToDate,
  kSessionHasPingListeners,
  kSessionHasAltsvcListeners
};

function debugSession(sessionType, message, ...args) {
  // debug('Http2Session %s: ' + message, sessionName(sessionType),
  //   ...args);
  // hilog.error(0x8691, 'ohos_grpc_node', `debugSessionObj: ${message}`, ...args);
}
function debugSessionObj(session, message, ...args) {
  debugSession(session[kType], message, ...args);
}

function isUint32(val) {
  return val >>> 0 === val;
}

/**
 * This util takes a string containing a URL and return the URL origin,
 * its meant to avoid calls to `new URL` constructor.
 * @param {string} url
 * @returns {URL['origin']}
 */
function getURLOrigin(url) {
  LogUtils.getInstance().info('ohos_gprs_node', 'getURLOrigin');
  return url;
  // return bindingUrl.getOrigin(url);
}

function initOriginSet(session) {
  let originSet = session[kState].originSet;
  if (originSet === undefined) {
    const socket = session[kSocket];
    session[kState].originSet = originSet = new Set();
    if (socket.servername != null) {
      let originString = `https://${socket.servername}`;
      if (socket.remotePort != null)
        originString += `:${socket.remotePort}`;
      // We have to ensure that it is a properly serialized
      // ASCII origin string. The socket.servername might not
      // be properly ASCII encoded.
      originSet.add(getURLOrigin(originString));
    }
  }
  return originSet;
}

// When the socket emits an error, destroy the associated Http2Session and
// forward it the same error.
function socketOnError(error) {
  const session = this[kBoundSession];
  if (session !== undefined) {
    // We can ignore ECONNRESET after GOAWAY was received as there's nothing
    // we can do and the other side is fully within its rights to do so.
    if (error.code === 'ECONNRESET' && session[kState].goawayCode !== null)
      return session.destroy();
    debugSessionObj(this, 'socket error [%s]', error.message);
    session.destroy(error);
  }
}

function socketOnClose() {
  const session = this[kBoundSession];
  if (session !== undefined) {
    debugSessionObj(session, 'socket closed');
    const err = session.connecting ? new Error('ERR_SOCKET_CLOSED') : null;
    const state = session[kState];
    state.streams.forEach((stream) => stream.close(constants.NGHTTP2_CANCEL));
    state.pendingStreams.forEach((stream) => stream.close(constants.NGHTTP2_CANCEL));
    session.close();
    session[kMaybeDestroy](err);
  }
}
enum Http2SessionStateIndex {
  IDX_SESSION_STATE_EFFECTIVE_LOCAL_WINDOW_SIZE,
  IDX_SESSION_STATE_EFFECTIVE_RECV_DATA_LENGTH,
  IDX_SESSION_STATE_NEXT_STREAM_ID,
  IDX_SESSION_STATE_LOCAL_WINDOW_SIZE,
  IDX_SESSION_STATE_LAST_PROC_STREAM_ID,
  IDX_SESSION_STATE_REMOTE_WINDOW_SIZE,
  IDX_SESSION_STATE_OUTBOUND_QUEUE_SIZE,
  IDX_SESSION_STATE_HD_DEFLATE_DYNAMIC_TABLE_SIZE,
  IDX_SESSION_STATE_HD_INFLATE_DYNAMIC_TABLE_SIZE,
  IDX_SESSION_STATE_COUNT
};
const sessionState = new Array(Http2SessionStateIndex.IDX_SESSION_STATE_COUNT);

function getSessionState(session) {
  session.refreshState();
  hilog.error(0x8691, 'ohos_grpc_node', 'no way to getSessionState');
  return {
    effectiveLocalWindowSize:
    sessionState[Http2SessionStateIndex.IDX_SESSION_STATE_EFFECTIVE_LOCAL_WINDOW_SIZE],
    effectiveRecvDataLength:
    sessionState[Http2SessionStateIndex.IDX_SESSION_STATE_EFFECTIVE_RECV_DATA_LENGTH],
    nextStreamID:
    sessionState[Http2SessionStateIndex.IDX_SESSION_STATE_NEXT_STREAM_ID],
    localWindowSize:
    sessionState[Http2SessionStateIndex.IDX_SESSION_STATE_LOCAL_WINDOW_SIZE],
    lastProcStreamID:
    sessionState[Http2SessionStateIndex.IDX_SESSION_STATE_LAST_PROC_STREAM_ID],
    remoteWindowSize:
    sessionState[Http2SessionStateIndex.IDX_SESSION_STATE_REMOTE_WINDOW_SIZE],
    outboundQueueSize:
    sessionState[Http2SessionStateIndex.IDX_SESSION_STATE_OUTBOUND_QUEUE_SIZE],
    deflateDynamicTableSize:
    sessionState[Http2SessionStateIndex.IDX_SESSION_STATE_HD_DEFLATE_DYNAMIC_TABLE_SIZE],
    inflateDynamicTableSize:
    sessionState[Http2SessionStateIndex.IDX_SESSION_STATE_HD_INFLATE_DYNAMIC_TABLE_SIZE],
  };
}

// Handles the on('stream') event for a session and forwards
// it on to the server object.
function sessionOnStream(stream, headers, flags, rawHeaders) {
  if (this[kServer] !== undefined)
    this[kServer].emit('stream', stream, headers, flags, rawHeaders);
}

// Creates the internal binding.Http2Session handle for an Http2Session
// instance. This occurs only after the socket connection has been
// established. Note: the binding.Http2Session will take over ownership
// of the socket. No other code should read from or write to the socket.
function setupHandle(socket, type, options) {
  // If the session has been destroyed, go ahead and emit 'connect',
  // but do nothing else. The various on('connect') handlers set by
  // core will check for session.destroyed before progressing, this
  // ensures that those at least get cleared out.
  if (this.destroyed) {
    // TODO
    // process.nextTick(emit, this, 'connect', this, socket);
    return;
  }

  // assert(socket._handle !== undefined,
  //   'Internal HTTP/2 Failure. The socket is not connected. Please ' +
  //     'report this as a bug in Node.js');

  debugSession(type, 'setting up session handle');
  this[kState].flags |= SESSION_FLAGS_READY;

  // updateOptionsBuffer(options);
  // if (options.remoteCustomSettings) {
  //   remoteCustomSettingsToBuffer(options.remoteCustomSettings);
  // }
  // const handle = new binding.Http2Session(type);
  // handle[kOwner] = this;
  //
  // if (typeof options.selectPadding === 'function')
  //   this[kSelectPadding] = options.selectPadding;
  // handle.consume(socket._handle);
  //
  // this[kHandle] = handle;
  // if (this[kNativeFields]) {
  //   // If some options have already been set before the handle existed, copy
  //   // those (and only those) that have manually been set over.
  //   this[kNativeFields].copyAssigned(handle.fields);
  // }

  // this[kNativeFields] = handle.fields;

  if (socket.encrypted) {
    this[kAlpnProtocol] = socket.alpnProtocol;
    this[kEncrypted] = true;
  } else {
    // 'h2c' is the protocol identifier for HTTP/2 over plain-text. We use
    // it here to identify any session that is not explicitly using an
    // encrypted socket.
    this[kAlpnProtocol] = 'h2c';
    this[kEncrypted] = false;
  }

  // if (isUint32(options.maxSessionInvalidFrames)) {
  //   const uint32 = new Uint32Array(
  //     this[kNativeFields].buffer, kSessionMaxInvalidFrames, 1);
  //   uint32[0] = options.maxSessionInvalidFrames;
  // }
  //
  // if (isUint32(options.maxSessionRejectedStreams)) {
  //   const uint32 = new Uint32Array(
  //     this[kNativeFields].buffer, kSessionMaxRejectedStreams, 1);
  //   uint32[0] = options.maxSessionRejectedStreams;
  // }

  const settings = typeof options.settings === 'object' ?
  options.settings : {};

  this.settings(settings);

  // if (type === constants.NGHTTP2_SESSION_SERVER &&
  // ArrayIsArray(options.origins)) {
  //   ReflectApply(this.origin, this, options.origins);
  // }
  //
  // process.nextTick(emit, this, 'connect', this, socket);
}

function validateString(value: string, label: string) {
  if ((typeof value) == 'string') {
    return true;
  } else {
    console.error(`${label} is not a valid integer.`);
    return false;
  }
}

function validateNumber(value: number, label: string) {
  if (Number.isInteger(value)) {
    return true;
  } else {
    console.error(`${label} is not a valid integer.`);
    return false;
  }
}

function validateBoolean(value: boolean, label: string) {
  if (value === true || value === false) {
    return true;
  } else {
    console.error(`${label} is not a valid integer.`);
    return false;
  }
}

function validateInt32(value: number, label: string, minValue: number) {
  if (Number.isInteger(value) && value >= minValue && value <= 0x7FFFFFFF && value >= -0x80000000) {
    return true;
  } else {
    console.error(`${label} must be an Int32 value between${minValue} and ${0x7FFFFFFF}.`);
    return false;
  }
}

function validateBuffer(payload, label) {
  if (payload instanceof ArrayBuffer || ArrayBuffer.isView(payload)) {
    return true;
  } else {
    console.error(`${label} must be an ArrayBuffer or a TypedArray.`);
    return false;
  }
}

function validateFunction(func, functionName) {
  if (typeof func === 'function') {
    return true;
  } else {
    console.error(`${functionName} must be a function.`);
    return false;
  }
}

// pingCallback() returns a function that is invoked when an HTTP2 PING
// frame acknowledgement is received. The ack is either true or false to
// indicate if the ping was successful or not. The duration indicates the
// number of milliseconds elapsed since the ping was sent and the ack
// received. The payload is a Buffer containing the 8 bytes of payload
// data received on the PING acknowledgement.
function pingCallback(cb) {
  return function pingCallback(ack, duration, payload) {
    if (ack) {
      cb(null, duration, payload);
    } else {
      cb(new Error('ERR_HTTP2_PING_CANCEL'));
    }
  };
}

// Submits a SETTINGS frame to be sent to the remote peer.
function submitSettings(settings, callback) {
  if (this.destroyed)
    return;
  debugSessionObj(this, 'submitting settings');
  this[kUpdateTimer]();
  //TODO: bind from c-code
  // updateSettingsBuffer(settings);
  // if (!this[kHandle].settings(settingsCallback.bind(this, callback))) {
  //   this.destroy(new ERR_HTTP2_MAX_PENDING_SETTINGS_ACK());
  // }
}

// Remote is a boolean. true to fetch remote settings, false to fetch local.
// this is only called internally
function getSettings(session, remote) {
  if (remote)
    session.remoteSettings();
  else
    session.localSettings();

  // TODO: bind from c-code
  // const toRet = {
  //   headerTableSize: settingsBuffer[IDX_SETTINGS_HEADER_TABLE_SIZE],
  //   enablePush: !!settingsBuffer[IDX_SETTINGS_ENABLE_PUSH],
  //   initialWindowSize: settingsBuffer[IDX_SETTINGS_INITIAL_WINDOW_SIZE],
  //   maxFrameSize: settingsBuffer[IDX_SETTINGS_MAX_FRAME_SIZE],
  //   maxConcurrentStreams: settingsBuffer[IDX_SETTINGS_MAX_CONCURRENT_STREAMS],
  //   maxHeaderListSize: settingsBuffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE],
  //   maxHeaderSize: settingsBuffer[IDX_SETTINGS_MAX_HEADER_LIST_SIZE],
  //   enableConnectProtocol:
  //   !!settingsBuffer[IDX_SETTINGS_ENABLE_CONNECT_PROTOCOL],
  // };
  // if (settingsBuffer[IDX_SETTINGS_FLAGS + 1]) toRet.customSettings = addCustomSettingsToObj();
  return null;
}

function closeSession(session, code, error) {
  debugSessionObj(session, 'start closing/destroying', error);

  const state = session[kState];
  state.flags |= SESSION_FLAGS_DESTROYED;
  state.destroyCode = code;

  // Clear timeout and remove timeout listeners.
  session.setTimeout(0);
  session.removeAllListeners('timeout');

  // Destroy any pending and open streams
  if (state.pendingStreams.size > 0 || state.streams.size > 0) {
    const cancel = error;
    state.pendingStreams.forEach((stream) => stream.destroy(cancel));
    state.streams.forEach((stream) => stream.destroy(error));
  }

  // Disassociate from the socket and server.
  const socket = session[kSocket];
  const handle = session[kHandle];

  // Destroy the handle if it exists at this point.
  // if (handle !== undefined) {
  //   handle.ondone = finishSessionClose.bind(null, session, error);
  //   handle.destroy(code, socket.destroyed);
  // } else {
  //   finishSessionClose(session, error);
  // }
}

// Upon creation, the Http2Session takes ownership of the socket. The session
// may not be ready to use immediately if the socket is not yet fully connected.
// In that case, the Http2Session will wait for the socket to connect. Once
// the Http2Session is ready, it will emit its own 'connect' event.
//
// The Http2Session.goaway() method will send a GOAWAY frame, signalling
// to the connected peer that a shutdown is in progress. Sending a goaway
// frame has no other effect, however.
//
// Receiving a GOAWAY frame will cause the Http2Session to first emit a 'goaway'
// event notifying the user that a shutdown is in progress. If the goaway
// error code equals 0 (NGHTTP2_NO_ERROR), session.close() will be called,
// causing the Http2Session to send its own GOAWAY frame and switch itself
// into a graceful closing state. In this state, new inbound or outbound
// Http2Streams will be rejected. Existing *pending* streams (those created
// but without an assigned stream ID or handle) will be destroyed with a
// cancel error. Existing open streams will be permitted to complete on their
// own. Once all existing streams close, session.destroy() will be called
// automatically.
//
// Calling session.destroy() will tear down the Http2Session immediately,
// making it no longer usable. Pending and existing streams will be destroyed.
// The bound socket will be destroyed. Once all resources have been freed up,
// the 'close' event will be emitted. Note that pending streams will be
// destroyed using a specific "ERR_HTTP2_STREAM_CANCEL" error. Existing open
// streams will be destroyed using the same error passed to session.destroy()
//
// If destroy is called with an error, an 'error' event will be emitted
// immediately following the 'close' event.
//
// The socket and Http2Session lifecycles are tightly bound. Once one is
// destroyed, the other should also be destroyed. When the socket is destroyed
// with an error, session.destroy() will be called with that same error.
// Likewise, when session.destroy() is called with an error, the same error
// will be sent to the socket.
// class Http2Session extends EventEmitter {
export class Http2Session {
  constructor(type, options, socket) {
    // super();

    // No validation is performed on the input parameters because this
    // constructor is not exported directly for users.

    // If the session property already exists on the socket,
    // then it has already been bound to an Http2Session instance
    // and cannot be attached again.
    if (socket[kBoundSession] !== undefined) {
      hilog.error(0x8691, 'ohos_grpc_node', 'constructor ERR_HTTP2_SOCKET_BOUND');
      throw new Error('ERR_HTTP2_SOCKET_BOUND');
    }


    socket[kBoundSession] = this;

    if (!socket._handle || !socket._handle.isStreamBase) {
      // socket = new JSStreamSocket(socket);
    }
    socket.on('error', socketOnError);
    socket.on('close', socketOnClose);

    this[kState] = {
      destroyCode: constants.NGHTTP2_NO_ERROR,
      flags: SESSION_FLAGS_PENDING,
      goawayCode: null,
      goawayLastStreamID: null,
      streams: new Map(),
      pendingStreams: new Set(),
      pendingAck: 0,
      shutdownWritableCalled: false,
      writeQueueSize: 0,
      originSet: undefined,
    };

    this[kEncrypted] = undefined;
    this[kAlpnProtocol] = undefined;
    this[kType] = type;
    this[kProxySocket] = null;
    this[kSocket] = socket;
    this[kTimeout] = null;
    this[kHandle] = undefined;

    // Do not use nagle's algorithm
    if (typeof socket.setNoDelay === 'function')
      socket.setNoDelay();

    // Disable TLS renegotiation on the socket
    if (typeof socket.disableRenegotiation === 'function')
      socket.disableRenegotiation();

    const setupFn = setupHandle.bind(this, socket, type, options);
    if (socket.connecting || socket.secureConnecting) {
      const connectEvent =
        socket instanceof socket.TLSSocket ? 'secureConnect' : 'connect';
      socket.once(connectEvent, () => {
        try {
          setupFn();
        } catch (error) {
          socket.destroy(error);
        }
      });
    } else {
      setupFn();
    }

    // this[kNativeFields] ||= trackAssignmentsTypedArray(new Uint8Array(kSessionUint8FieldCount));
    // this.on('newListener', sessionListenerAdded);
    // this.on('removeListener', sessionListenerRemoved);

    // Process data on the next tick - a remoteSettings handler may be attached.
    // https://github.com/nodejs/node/issues/35981
    // process.nextTick(() => {
    //   // Socket already has some buffered data - emulate receiving it
    //   // https://github.com/nodejs/node/issues/35475
    //   // https://github.com/nodejs/node/issues/34532
    //   if (socket.readableLength) {
    //     let buf;
    //     while ((buf = socket.read()) !== null) {
    //       debugSession(type, `${buf.length} bytes already in buffer`);
    //       this[kHandle].receive(buf);
    //     }
    //   }
    // });

    debugSession(type, 'created');
  }

  // Returns undefined if the socket is not yet connected, true if the
  // socket is a TLSSocket, and false if it is not.
  get encrypted() {
    return this[kEncrypted];
  }

  // Returns undefined if the socket is not yet connected, `h2` if the
  // socket is a TLSSocket and the alpnProtocol is `h2`, or `h2c` if the
  // socket is not a TLSSocket.
  get alpnProtocol() {
    return this[kAlpnProtocol];
  }

  // TODO(jasnell): originSet is being added in preparation for ORIGIN frame
  // support. At the current time, the ORIGIN frame specification is awaiting
  // publication as an RFC and is awaiting implementation in nghttp2. Once
  // added, an ORIGIN frame will add to the origins included in the origin
  // set. 421 responses will remove origins from the set.
  get originSet() {
    if (!this.encrypted || this.destroyed)
      return undefined;
    return Array.from(initOriginSet(this));
  }

  // True if the Http2Session is still waiting for the socket to connect
  get connecting() {
    return (this[kState].flags & SESSION_FLAGS_READY) === 0;
  }

  // True if Http2Session.prototype.close() has been called
  get closed() {
    return !!(this[kState].flags & SESSION_FLAGS_CLOSED);
  }

  // True if Http2Session.prototype.destroy() has been called
  destroyed(arg?) {
    return !!(this[kState].flags & SESSION_FLAGS_DESTROYED);
  }

  // Resets the timeout counter
  [kUpdateTimer]() {
    if (this.destroyed)
      return;
    if (this[kTimeout]) this[kTimeout].refresh();
  }

  // Sets the id of the next stream to be created by this Http2Session.
  // The value must be a number in the range 0 <= n <= kMaxStreams. The
  // value also needs to be larger than the current next stream ID.
  setNextStreamID(id) {
    if (this.destroyed) {
      hilog.error(0x8691, 'ohos_grpc_node', 'ERR_HTTP2_INVALID_SESSION');
      throw new Error('ERR_HTTP2_INVALID_SESSION');
    }

    validateNumber(id, 'id');
    if (id <= 0 || id > kMaxStreams) {
      hilog.error(0x8691, 'ohos_grpc_node', `ERR_OUT_OF_RANGE(${id} > 0 and <= ${kMaxStreams}`);
      throw new Error(`ERR_OUT_OF_RANGE(${id} > 0 and <= ${kMaxStreams}`);
    }

    this[kHandle].setNextStreamID(id);
  }

  // Sets the local window size (local endpoints's window size)
  // Returns 0 if success or throw an exception if NGHTTP2_ERR_NOMEM
  // if the window allocation fails
  setLocalWindowSize(windowSize) {
    if (this.destroyed) {
      hilog.error(0x8691, 'ohos_grpc_node', 'ERR_HTTP2_INVALID_SESSION');
      throw new Error('ERR_HTTP2_INVALID_SESSION');
    }

    validateInt32(windowSize, 'windowSize', 0);
    const ret = this[kHandle].setLocalWindowSize(windowSize);

    if (ret === expectedNGConstants.NGHTTP2_ERR_NOMEM) {
      hilog.error(0x8691, 'ohos_grpc_node', 'ERR_HTTP2_NO_MEM');
      throw new Error('ERR_HTTP2_NO_MEM');
    }
  }

  // If ping is called while we are still connecting, or after close() has
  // been called, the ping callback will be invoked immediately with a ping
  // cancelled error and a duration of 0.0.

  ping(payload?, callback?) {
    if (this.destroyed) {
      hilog.error(0x8691, 'ohos_grpc_node', 'ERR_HTTP2_INVALID_SESSION');
      throw new Error('ERR_HTTP2_INVALID_SESSION');
    }

    if (typeof payload === 'function') {
      callback = payload;
      payload = undefined;
    }
    if (payload) {
      validateBuffer(payload, 'payload');
    }
    if (payload && payload.length !== 8) {
      throw new Error('ERR_HTTP2_PING_LENGTH');
    }
    validateFunction(callback, 'callback');

    const cb = pingCallback(callback);
    if (this.connecting || this.closed) {
      // process.nextTick(cb, false, 0.0, payload);
      return;
    }

    return this[kHandle].ping(payload, cb);
  }

  // [kInspect](depth, opts) {
  //   if (typeof depth === 'number' && depth < 0) {
  //     return this;
  //   }
  //
  //   const obj = {
  //     type: this[kType],
  //     closed: this.closed,
  //     destroyed: this.destroyed,
  //     state: this.state,
  //     localSettings: this.localSettings,
  //     remoteSettings: this.remoteSettings,
  //   };
  //   return `Http2Session ${JSON.stringify(obj)}`;
  // }

  // The socket owned by this session
  get socket() {
    const proxySocket = this[kProxySocket];
    if (proxySocket === null)
      return this[kProxySocket] = new Proxy(this, proxySocketHandler);
    return proxySocket;
  }

  // The session type
  get type() {
    return this[kType];
  }

  // If a GOAWAY frame has been received, gives the error code specified
  get goawayCode() {
    return this[kState].goawayCode || expectedNGConstants.NGHTTP2_NO_ERROR;
  }

  // If a GOAWAY frame has been received, gives the last stream ID reported
  get goawayLastStreamID() {
    return this[kState].goawayLastStreamID || 0;
  }

  // True if the Http2Session is waiting for a settings acknowledgement
  get pendingSettingsAck() {
    return this[kState].pendingAck > 0;
  }

  // Retrieves state information for the Http2Session
  get state() {
    return this.connecting || this.destroyed ?
      {localWindowSize:0,remoteWindowSize:0} : getSessionState(this[kHandle]);
  }

  // The settings currently in effect for the local peer. These will
  // be updated only when a settings acknowledgement has been received.
  get localSettings() {
    const settings = this[kLocalSettings];
    if (settings !== undefined)
      return settings;

    if (this.destroyed || this.connecting)
      return {};

    return this[kLocalSettings] = getSettings(this[kHandle], false); // Local
  }

  // The settings currently in effect for the remote peer.
  get remoteSettings() {
    //TODO: bind from bit
    // if (this[kNativeFields][kBitfield] &
    //   (1 << SessionBitfieldFlags.kSessionRemoteSettingsIsUpToDate)) {
    //   const settings = this[kRemoteSettings];
    //   if (settings !== undefined) {
    //     return settings;
    //   }
    // }
    //
    // if (this.destroyed || this.connecting)
    //   return {};
    //
    // this[kNativeFields][kBitfield] |= (1 << kSessionRemoteSettingsIsUpToDate);
    return this[kRemoteSettings] = getSettings(this[kHandle], true); // Remote
  }

  // Submits a SETTINGS frame to be sent to the remote peer.
  settings(settings, callback) {
    if (this.destroyed) {
      hilog.error(0x8691, 'ohos_grpc_node', 'ERR_HTTP2_INVALID_SESSION');
      throw new Error('ERR_HTTP2_INVALID_SESSION');
    }

    //TODO: bind from c-code
    // assertIsObject(settings, 'settings');
    // validateSettings(settings);

    if (callback) {
      validateFunction(callback, 'callback');
    }
    debugSessionObj(this, 'sending settings');

    this[kState].pendingAck++;

    const settingsFn = submitSettings.bind(this, { ...settings }, callback);
    if (this.connecting) {
      // this.once('connect', settingsFn);
      return;
    }
    settingsFn();
  }

  // Submits a GOAWAY frame to be sent to the remote peer. Note that this
  // is only a notification, and does not affect the usable state of the
  // session with the notable exception that new incoming streams will
  // be rejected automatically.
  goaway(code = constants.NGHTTP2_NO_ERROR, lastStreamID = 0, opaqueData) {
    if (this.destroyed) {
      hilog.error(0x8691, 'ohos_grpc_node', 'ERR_HTTP2_INVALID_SESSION');
      throw new Error('ERR_HTTP2_INVALID_SESSION');
    }


    if (opaqueData !== undefined) {
      validateBuffer(opaqueData, 'opaqueData');
    }
    validateNumber(code, 'code');
    validateNumber(lastStreamID, 'lastStreamID');

    // const goawayFn = submitGoaway.bind(this, code, lastStreamID, opaqueData);
    // if (this.connecting) {
    //   this.once('connect', goawayFn);
    //   return;
    // }
    // goawayFn();
  }

  // Destroy the Http2Session, making it no longer usable and cancelling
  // any pending activity.
  destroy(number?): void;
  destroy(error?:number, code?: number) {
    if (this.destroyed)
      return;

    debugSessionObj(this, 'destroying');

    if (typeof error === 'number') {
      code = error;
      error =
        code !== constants.NGHTTP2_NO_ERROR ?
          code : undefined;
    }
    if (code === undefined && error != null)
      code = constants.NGHTTP2_INTERNAL_ERROR;

    closeSession(this, code, error);
  }

  // Closing the session will:
  // 1. Send a goaway frame
  // 2. Mark the session as closed
  // 3. Prevent new inbound or outbound streams from being opened
  // 4. Optionally register a 'close' event handler
  // 5. Will cause the session to automatically destroy after the
  //    last currently open Http2Stream closes.
  //
  // Close always assumes a good, non-error shutdown (NGHTTP_NO_ERROR)
  //
  // If the session has not connected yet, the closed flag will still be
  // set but the goaway will not be sent until after the connect event
  // is emitted.
  close(callback?) {
    if (this.closed || this.destroyed)
      return;
    debugSessionObj(this, 'marking session closed');
    this[kState].flags |= SESSION_FLAGS_CLOSED;
    if (typeof callback === 'function') {
      callback()
    }
    //   this.once('close', callback);
    // this.goaway();
    // this[kMaybeDestroy]();
  }

  // [EventEmitter.captureRejectionSymbol](err, event, ...args) {
  //   switch (event) {
  //     case 'stream': {
  //       const stream = args[0];
  //       stream.destroy(err);
  //       break;
  //     }
  //     default:
  //       this.destroy(err);
  //   }
  // }

  // Destroy the session if:
  // * error is not undefined/null
  // * session is closed and there are no more pending or open streams
  [kMaybeDestroy](error) {
    if (error == null) {
      const state = this[kState];
      // Do not destroy if we're not closed and there are pending/open streams
      if (!this.closed ||
        state.streams.size > 0 ||
        state.pendingStreams.size > 0) {
        return;
      }
    }
    // this.destroy(error);
  }

  _onTimeout() {
    hilog.error(0x8691, 'ohos_grpc_node', '_onTimeout')
    // callTimeout(this, this);
  }

  ref() {
    if (this[kSocket]) {
      this[kSocket].ref();
    }
  }

  unref() {
    if (this[kSocket]) {
      this[kSocket].unref();
    }
  }
}

const kSessionPriorityListenerCount = 10;
// ServerHttp2Session instances should never have to wait for the socket
// to connect as they are always created after the socket has already been
// established.
export class ServerHttp2Session extends Http2Session {
  constructor(options, socket, server) {
    super(expectedNGConstants.NGHTTP2_SESSION_SERVER, options, socket);
    this[kServer] = server;
    // This is a bit inaccurate because it does not reflect changes to
    // number of listeners made after the session was created. This should
    // not be an issue in practice. Additionally, the 'priority' event on
    // server instances (or any other object) is fully undocumented.
    this[kNativeFields][kSessionPriorityListenerCount] =
      server ? server.listenerCount('priority') : 0;
  }

  on(event: 'response',
    listener: (headers: incomingHttpHeaders & IncomingHttpStatusHeader, flags: number) => void,
  ): void;
  on(event: 'data', callback: (data: Buffer) => void) : void;
  on(event: 'error', callback: (err: Error) => void): void;
  on(event: 'trailers', callback: (headers: IncomingHttpHeaders) => void): void;
  on(event: 'end', callback: () => void): void;
  on(event: 'close', callback: () => void): void;
  on(event: 'remoteSettings', callback: (settings: Settings) => void): void;
  on(event: string, callback: (...args: any[])=>void) {

  }
  once(event: 'close', callback: () => void): void;
  once(event: 'once', callback: (errorCode: number, lastStreamID: number, opaqueData?: Buffer) => void): void;
  once(event: 'goaway', callback: (errorCode: number, lastStreamID: number, opaqueData?: Buffer) => void): void;
  once(event: 'error', callback: (error) => void): void;
  once(event: string, callback: (...args: any[])=>void) {

  }

  get server() {
    return this[kServer];
  }

  // Submits an altsvc frame to be sent to the client. `stream` is a
  // numeric Stream ID. origin is a URL string that will be used to get
  // the origin. alt is a string containing the altsvc details. No fancy
  // API is provided for that.
  altsvc(alt, originOrStream) {
    if (this.destroyed) {
      hilog.error(0x8691, 'ohos_grpc_node', 'ERR_HTTP2_INVALID_SESSION');
      throw new Error('ERR_HTTP2_INVALID_SESSION');
    }

    let stream = 0;
    let origin;

    if (typeof originOrStream === 'string') {
      origin = getURLOrigin(originOrStream);
      if (origin === 'null') {
        hilog.error(0x8691, 'ohos_grpc_node', 'ERR_HTTP2_ALTSVC_INVALID_ORIGIN');
        throw new Error('ERR_HTTP2_ALTSVC_INVALID_ORIGIN');
      }
    } else if (typeof originOrStream === 'number') {
      if (originOrStream >>> 0 !== originOrStream || originOrStream === 0) {
        hilog.error(0x8691, 'ohos_grpc_node', 'ERR_OUT_OF_RANGE(\'originOrStream> 0 && < $\{2 ** 32} originOrStream');
        throw new Error(`ERR_OUT_OF_RANGE('originOrStream> 0 && < ${2 ** 32} originOrStream`);
      }
      stream = originOrStream;
    } else if (originOrStream !== undefined) {
      // Allow origin to be passed a URL or object with origin property
      if (originOrStream !== null && typeof originOrStream === 'object')
        origin = originOrStream.origin;
      // Note: if originOrStream is an object with an origin property other
      // than a URL, then it is possible that origin will be malformed.
      // We do not verify that here. Users who go that route need to
      // ensure they are doing the right thing or the payload data will
      // be invalid.
      if (typeof origin !== 'string') {
        hilog.error(0x8691, 'ohos_grpc_node', 'ERR_HTTP2_ALTSVC_INVALID_ORIGIN');
        throw new Error(`ERR_INVALID_ARG_TYPE(originOrStream['string', 'number', 'URL', 'object'],
          originOrStream`);
      } else if (origin === 'null' || origin.length === 0) {
        hilog.error(0x8691, 'ohos_grpc_node', 'ERR_HTTP2_ALTSVC_INVALID_ORIGIN');
        throw new Error(`ERR_HTTP2_ALTSVC_INVALID_ORIGIN`);
      }
    }

    // validateString(alt, 'alt');
    // if (!kQuotedString.test(alt))
    //   throw new ERR_INVALID_CHAR('alt');

    // Max length permitted for ALTSVC
    if ((alt.length + (origin !== undefined ? origin.length : 0)) > kMaxALTSVC) {
      hilog.error(0x8691, 'ohos_grpc_node', 'ERR_HTTP2_ALTSVC_LENGTH');
      throw new Error('ERR_HTTP2_ALTSVC_LENGTH');
    }

    this[kHandle].altsvc(stream, origin || '', alt);
  }

  // Submits an origin frame to be sent.
  origin(...origins) {
    if (this.destroyed) {
      hilog.error(0x8691, 'ohos_grpc_node', 'ERR_HTTP2_INVALID_SESSION');
      throw new Error('ERR_HTTP2_INVALID_SESSION');
    }

    if (origins.length === 0)
      return;

    let arr = '';
    let len = 0;
    const count = origins.length;
    for (let i = 0; i < count; i++) {
      let origin = origins[i];
      if (typeof origin === 'string') {
        origin = getURLOrigin(origin);
      } else if (origin != null && typeof origin === 'object') {
        origin = origin.origin;
      }
      validateString(origin, 'origin');
      if (origin === 'null') {
        hilog.error(0x8691, 'ohos_grpc_node', 'ERR_HTTP2_INVALID_ORIGIN');
        throw new Error('ERR_HTTP2_INVALID_ORIGIN');
      }

      arr += `${origin}\0`;
      len += origin.length;
    }

    if (len > kMaxALTSVC) {
      hilog.error(0x8691, 'ohos_grpc_node', 'ERR_HTTP2_ORIGIN_LENGTH');
      throw new Error('ERR_HTTP2_ORIGIN_LENGTH');
    }

    this[kHandle].origin(arr, count);
  }

}

// ClientHttp2Session instances have to wait for the socket to connect after
// they have been created. Various operations such as request() may be used,
// but the actual protocol communication will only occur after the socket
// has been connected.
const HTTP2_METHOD_GET = 2;
const HTTP2_METHOD_CONNECT = 7;

const NGHTTP2_ERR_INVALID_ARGUMENT = -501;
const NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE = -509;

const STREAM_OPTION_EMPTY_PAYLOAD = 0x1;

// Stream might have trailing headers
const STREAM_OPTION_GET_TRAILERS = 0x2;

function getAuthority(headers) {
  // For non-CONNECT requests, HTTP/2 allows either :authority
  // or Host to be used equivalently. The first is preferred
  // when making HTTP/2 requests, and the latter is preferred
  // when converting from an HTTP/1 message.
  if (headers[expectedHeaderNames.HTTP2_HEADER_AUTHORITY] !== undefined)
    return headers[expectedHeaderNames.HTTP2_HEADER_AUTHORITY];
  if (headers[expectedHeaderNames.HTTP2_HEADER_HOST] !== undefined)
    return headers[expectedHeaderNames.HTTP2_HEADER_HOST];
}
// Validates that priority options are correct, specifically:
// 1. options.weight must be a number
// 2. options.parent must be a positive number
// 3. options.exclusive must be a boolean
// 4. if specified, options.silent must be a boolean
//
// Also sets the default priority options if they are not set.
const setAndValidatePriorityOptions = ((options) => {
  if (options.weight === undefined) {
    options.weight = expectedNGConstants.NGHTTP2_DEFAULT_WEIGHT;
  } else {
    validateNumber(options.weight, 'options.weight');
  }

  if (options.parent === undefined) {
    options.parent = 0;
  } else {
    validateNumber(options.parent, 'options.parent');
  }

  if (options.exclusive === undefined) {
    options.exclusive = false;
  } else {
    validateBoolean(options.exclusive, 'options.exclusive');
  }

  if (options.silent === undefined) {
    options.silent = false;
  } else {
    validateBoolean(options.silent, 'options.silent');
  }
});

// When a ClientHttp2Session is first created, the socket may not yet be
// connected. If request() is called during this time, the actual request
// will be deferred until the socket is ready to go.
function requestOnConnect(headers, options) {
  const session = this[kSession];

  // At this point, the stream should have already been destroyed during
  // the session.destroy() method. Do nothing else.
  if (session === undefined || session.destroyed)
    return;

  // If the session was closed while waiting for the connect, destroy
  // the stream and do not continue with the request.
  if (session.closed) {
    const err = new Error('ERR_HTTP2_GOAWAY_SESSION');
    this.destroy(err);
    return;
  }

  debugSessionObj(session, 'connected, initializing request');

  let streamOptions = 0;
  if (options.endStream)
    streamOptions |= STREAM_OPTION_EMPTY_PAYLOAD;

  if (options.waitForTrailers)
    streamOptions |= STREAM_OPTION_GET_TRAILERS;

  // `ret` will be either the reserved stream ID (if positive)
  // or an error code (if negative)
  const ret = session[kHandle].request(headers,
    streamOptions,
    options.parent | 0,
    options.weight | 0,
    !!options.exclusive);

  // In an error condition, one of three possible response codes will be
  // possible:
  // * NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE - Maximum stream ID is reached, this
  //   is fatal for the session
  // * NGHTTP2_ERR_INVALID_ARGUMENT - Stream was made dependent on itself, this
  //   impacts on this stream.
  // For the first two, emit the error on the session,
  // For the third, emit the error on the stream, it will bubble up to the
  // session if not handled.
  if (typeof ret === 'number') {
    let err;
    switch (ret) {
      case NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE:
        err = new Error('ERR_HTTP2_OUT_OF_STREAMS');
        this.destroy(err);
        break;
      case NGHTTP2_ERR_INVALID_ARGUMENT:
        err = new Error('ERR_HTTP2_STREAM_SELF_DEPENDENCY');
        this.destroy(err);
        break;
      default:
        // session.destroy(new NghttpError(ret));
    }
    return;
  }
  this[kInit](ret.id(), ret);
}

// const HTTP2_METHOD_DELETE = 'DELETE';
// const HTTP2_METHOD_GET = 'GET';
// const HTTP2_METHOD_HEAD = 'HEAD'
// The HTTP methods in this set are specifically defined as assigning no
// meaning to the request payload. By default, unless the user explicitly
// overrides the endStream option on the request method, the endStream
// option will be defaulted to true when these methods are used.
const kNoPayloadMethods = new Set([
  'DELETE',
  'GET',
  'HEAD',
]);

function isPayloadMeaningless(method) {
  return kNoPayloadMethods.has(method);
}
enum Http2StreamStateIndex {
  IDX_STREAM_STATE,
  IDX_STREAM_STATE_WEIGHT,
  IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT,
  IDX_STREAM_STATE_LOCAL_CLOSE,
  IDX_STREAM_STATE_REMOTE_CLOSE,
  IDX_STREAM_STATE_LOCAL_WINDOW_SIZE,
  IDX_STREAM_STATE_COUNT
};
const streamState = new Array(Http2StreamStateIndex.IDX_STREAM_STATE_COUNT);
function getStreamState(stream) {
  stream.refreshState();
  return {
    state: streamState[Http2StreamStateIndex.IDX_STREAM_STATE],
    weight: streamState[Http2StreamStateIndex.IDX_STREAM_STATE_WEIGHT],
    sumDependencyWeight: streamState[Http2StreamStateIndex.IDX_STREAM_STATE_SUM_DEPENDENCY_WEIGHT],
    localClose: streamState[Http2StreamStateIndex.IDX_STREAM_STATE_LOCAL_CLOSE],
    remoteClose: streamState[Http2StreamStateIndex.IDX_STREAM_STATE_REMOTE_CLOSE],
    localWindowSize: streamState[Http2StreamStateIndex.IDX_STREAM_STATE_LOCAL_WINDOW_SIZE],
  };
}

// An Http2Stream is a Duplex stream that is backed by a
// node::http2::Http2Stream handle implementing StreamBase.
class Http2Stream {
  constructor(session, options) {
    options.allowHalfOpen = true;
    options.decodeStrings = false;
    options.autoDestroy = false;
    // super();
    // this[async_id_symbol] = -1;
    //
    // // Corking the stream automatically allows writes to happen
    // // but ensures that those are buffered until the handle has
    // // been assigned.
    // this.cork();
    this[kSession] = session;
    session[kState].pendingStreams.add(this);

    // Allow our logic for determining whether any reads have happened to
    // work in all situations. This is similar to what we do in _http_incoming.
    // this._readableState.readingMore = true;

    this[kTimeout] = null;

    this[kState] = {
      didRead: false,
      flags: STREAM_FLAGS_PENDING,
      rstCode: constants.NGHTTP2_NO_ERROR,
      writeQueueSize: 0,
      trailersReady: false,
      endAfterHeaders: false,
    };

    // Fields used by the compat API to avoid megamorphisms.
    this[kRequest] = null;
    this[kProxySocket] = null;

    // this.on('pause', streamOnPause);
    //
    // this.on('newListener', streamListenerAdded);
    // this.on('removeListener', streamListenerRemoved);
  }
  writableLength: number;

  push(buf){};
  [kUpdateTimer]() {
    // if (this.destroyed)
    //   return;
    if (this[kTimeout])
      this[kTimeout].refresh();
    if (this[kSession])
      this[kSession][kUpdateTimer]();
  }

  [kInit](id, handle) {
    const state = this[kState];
    state.flags |= STREAM_FLAGS_READY;

    const session = this[kSession];
    session[kState].pendingStreams.delete(this);
    session[kState].streams.set(id, this);

    this[kID] = id;
    // this[async_id_symbol] = handle.getAsyncId();
    // handle[kOwner] = this;
    // this[kHandle] = handle;
    // handle.onread = onStreamRead;
    // this.uncork();
    // this.emit('ready');
  }

  // [kInspect](depth, opts) {
  //   if (typeof depth === 'number' && depth < 0)
  //     return this;
  //
  //   const obj = {
  //     id: this[kID] || '<pending>',
  //     closed: this.closed,
  //     destroyed: this.destroyed,
  //     state: this.state,
  //     readableState: this._readableState,
  //     writableState: this._writableState,
  //   };
  //   return `Http2Stream ${format(obj)}`;
  // }

  get bufferSize() {
    // `bufferSize` properties of `net.Socket` are `undefined` when
    // their `_handle` are falsy. Here we avoid the behavior.
    return this[kState].writeQueueSize + this.writableLength;
  }

  get endAfterHeaders() {
    return this[kState].endAfterHeaders;
  }

  get sentHeaders() {
    return this[kSentHeaders];
  }

  get sentTrailers() {
    return this[kSentTrailers];
  }

  get sentInfoHeaders() {
    return this[kInfoHeaders];
  }

  get pending() {
    return this[kID] === undefined;
  }

  // The id of the Http2Stream, will be undefined if the socket is not
  // yet connected.
  get id() {
    return this[kID];
  }

  // The Http2Session that owns this Http2Stream.
  get session() {
    return this[kSession];
  }

  _onTimeout() {
    // callTimeout(this, this[kSession]);
  }

  // True if the HEADERS frame has been sent
  get headersSent() {
    return !!(this[kState].flags & STREAM_FLAGS_HEADERS_SENT);
  }

  // True if the Http2Stream was aborted abnormally.
  get aborted() {
    return !!(this[kState].flags & STREAM_FLAGS_ABORTED);
  }

  // True if dealing with a HEAD request
  get headRequest() {
    return !!(this[kState].flags & STREAM_FLAGS_HEAD_REQUEST);
  }

  // The error code reported when this Http2Stream was closed.
  get rstCode() {
    return this[kState].rstCode;
  }

  // State information for the Http2Stream
  get state() {
    const id = this[kID];
    // if (this.destroyed || id === undefined)
    //   return {};
    return getStreamState(this[kHandle]);
  }

  // [kProceed]() {
  //   assert.fail('Implementers MUST implement this. Please report this as a ' +
  //     'bug in Node.js');
  // }

  [kAfterAsyncWrite]({ bytes }) {
    this[kState].writeQueueSize -= bytes;

    if (this.session !== undefined)
      this.session[kState].writeQueueSize -= bytes;
  }

  [kWriteGeneric](writev, data, encoding, cb) {
    // When the Http2Stream is first created, it is corked until the
    // handle and the stream ID is assigned. However, if the user calls
    // uncork() before that happens, the Duplex will attempt to pass
    // writes through. Those need to be queued up here.
    if (this.pending) {
      // this.once(
      //   'ready',
      //   this[kWriteGeneric].bind(this, writev, data, encoding, cb),
      // );
      return;
    }

    // If the stream has been destroyed, there's nothing else we can do
    // because the handle has been destroyed. This should only be an
    // issue if a write occurs before the 'ready' event in the case where
    // the duplex is uncorked before the stream is ready to go. In that
    // case, drop the data on the floor. An error should have already been
    // emitted.
    // if (this.destroyed)
    //   return;

    this[kUpdateTimer]();
    if (!this.headersSent)
      this[kProceed]();

    let req;

    let waitingForWriteCallback = true;
    let waitingForEndCheck = true;
    let writeCallbackErr;
    let endCheckCallbackErr;
    const done = () => {
      if (waitingForEndCheck || waitingForWriteCallback) return;
      // const err = aggregateTwoErrors(endCheckCallbackErr, writeCallbackErr);
      // // writeGeneric does not destroy on error and
      // // we cannot enable autoDestroy,
      // // so make sure to destroy on error.
      // if (err) {
      //   this.destroy(err);
      // }
      // cb(err);
    };
    const writeCallback = (err) => {
      waitingForWriteCallback = false;
      writeCallbackErr = err;
      done();
    };
    const endCheckCallback = (err) => {
      waitingForEndCheck = false;
      endCheckCallbackErr = err;
      done();
    };
    // Shutdown write stream right after last chunk is sent
    // so final DATA frame can include END_STREAM flag
    // process.nextTick(() => {
    //   if (writeCallbackErr ||
    //     !this._writableState.ending ||
    //   this._writableState.buffered.length ||
    //     (this[kState].flags & STREAM_FLAGS_HAS_TRAILERS))
    //     return endCheckCallback();
    //   debugStreamObj(this, 'shutting down writable on last write');
    //   shutdownWritable.call(this, endCheckCallback);
    // });
    //
    // if (writev)
    //   req = writevGeneric(this, data, writeCallback);
    // else
    //   req = writeGeneric(this, data, encoding, writeCallback);
    //
    // trackWriteState(this, req.bytes);
  }

  _write(data, encoding, cb) {
    this[kWriteGeneric](false, data, encoding, cb);
  }

  _writev(data, cb) {
    this[kWriteGeneric](true, data, '', cb);
  }

  _final(cb) {
    // if (this.pending) {
    //   this.once('ready', () => this._final(cb));
    //   return;
    // }
    // debugStreamObj(this, 'shutting down writable on _final');
    // ReflectApply(shutdownWritable, this, [cb]);
  }

  _read(nread) {
    // if (this.destroyed) {
    //   this.push(null);
    //   return;
    // }
    // if (!this[kState].didRead) {
    //   this._readableState.readingMore = false;
    //   this[kState].didRead = true;
    // }
    // if (!this.pending) {
    //   streamOnResume.call(this);
    // } else {
    //   this.once('ready', streamOnResume);
    // }
  }

  priority(options) {
    // if (this.destroyed)
    //   throw new ERR_HTTP2_INVALID_STREAM();
    //
    // assertIsObject(options, 'options');
    // options = { ...options };
    // setAndValidatePriorityOptions(options);
    //
    // const priorityFn = submitPriority.bind(this, options);
    //
    // // If the handle has not yet been assigned, queue up the priority
    // // frame to be sent as soon as the ready event is emitted.
    // if (this.pending) {
    //   this.once('ready', priorityFn);
    //   return;
    // }
    // priorityFn();
  }

  sendTrailers(headers) {
    // if (this.destroyed || this.closed)
    //   throw new ERR_HTTP2_INVALID_STREAM();
    // if (this[kSentTrailers])
    //   throw new ERR_HTTP2_TRAILERS_ALREADY_SENT();
    // if (!this[kState].trailersReady)
    //   throw new ERR_HTTP2_TRAILERS_NOT_READY();
    //
    // assertIsObject(headers, 'headers');
    // headers = ObjectAssign({ __proto__: null }, headers);
    //
    // debugStreamObj(this, 'sending trailers');
    //
    // this[kUpdateTimer]();
    //
    // const headersList = mapToHeaders(headers, assertValidPseudoHeaderTrailer);
    // this[kSentTrailers] = headers;
    //
    // // Send the trailers in setImmediate so we don't do it on nghttp2 stack.
    // setImmediate(finishSendTrailers, this, headersList);
  }

  get closed() {
    return !!(this[kState].flags & STREAM_FLAGS_CLOSED);
  }

  // Close initiates closing the Http2Stream instance by sending an RST_STREAM
  // frame to the connected peer. The readable and writable sides of the
  // Http2Stream duplex are closed and the timeout timer is cleared. If
  // a callback is passed, it is registered to listen for the 'close' event.
  //
  // If the handle and stream ID have not been assigned yet, the close
  // will be queued up to wait for the ready event. As soon as the stream ID
  // is determined, the close will proceed.
  //
  // Submitting the RST_STREAM frame to the underlying handle will cause
  // the Http2Stream to be closed and ultimately destroyed. After calling
  // close, it is still possible to queue up PRIORITY and RST_STREAM frames,
  // but no DATA and HEADERS frames may be sent.
  close(code = constants.NGHTTP2_NO_ERROR, callback) {
    validateInt32(code, 'code', 0);

    if (callback !== undefined) {
      validateFunction(callback, 'callback');
    }

    if (this.closed)
      return;

    // if (callback !== undefined)
    //   this.once('close', callback);
    //
    // closeStream(this, code);
  }

  // Called by this.destroy().
  // * Will submit an RST stream to shutdown the stream if necessary.
  //   This will cause the internal resources to be released.
  // * Then cleans up the resources on the js side
  _destroy(err, callback) {
    const session = this[kSession];
    const handle = this[kHandle];
    const id = this[kID];

    // debugStream(this[kID] || 'pending', session[kType], 'destroying stream');

    const state = this[kState];
    const sessionState = session[kState];
    const sessionCode = sessionState.goawayCode || sessionState.destroyCode;

    // If a stream has already closed successfully, there is no error
    // to report from this stream, even if the session has errored.
    // This can happen if the stream was already in process of destroying
    // after a successful close, but the session had a error between
    // this stream's close and destroy operations.
    // Previously, this always overrode a successful close operation code
    // NGHTTP2_NO_ERROR (0) with sessionCode because the use of the || operator.
    let code = this.closed ? this.rstCode : sessionCode;
    // if (err != null) {
    //   if (sessionCode) {
    //     code = sessionCode;
    //   } else if (err instanceof AbortError) {
    //     // Enables using AbortController to cancel requests with RST code 8.
    //     code = NGHTTP2_CANCEL;
    //   } else {
    //     code = NGHTTP2_INTERNAL_ERROR;
    //   }
    // }
    const hasHandle = handle !== undefined;

    // if (!this.closed)
    //   closeStream(this, code, hasHandle ? kForceRstStream : kNoRstStream);
    this.push(null);

    if (hasHandle) {
      handle.destroy();
      sessionState.streams.delete(id);
    } else {
      sessionState.pendingStreams.delete(this);
    }

    // Adjust the write queue size for accounting
    sessionState.writeQueueSize -= state.writeQueueSize;
    state.writeQueueSize = 0;

    // RST code 8 not emitted as an error as its used by clients to signify
    // abort and is already covered by aborted event, also allows more
    // seamless compatibility with http1
    // if (err == null && code !== NGHTTP2_NO_ERROR && code !== NGHTTP2_CANCEL)
    //   err = new ERR_HTTP2_STREAM_ERROR(nameForErrorCode[code] || code);

    this[kSession] = undefined;
    this[kHandle] = undefined;

    // This notifies the session that this stream has been destroyed and
    // gives the session the opportunity to clean itself up. The session
    // will destroy if it has been closed and there are no other open or
    // pending streams. Delay with setImmediate so we don't do it on the
    // nghttp2 stack.
    // setImmediate(() => {
    //   session[kMaybeDestroy]();
    // });
    callback(err);
  }
  // The Http2Stream can be destroyed if it has closed and if the readable
  // side has received the final chunk.
  // [kMaybeDestroy](code = NGHTTP2_NO_ERROR) {
  //   if (code !== NGHTTP2_NO_ERROR) {
  //     this.destroy();
  //     return;
  //   }
  //
  //   if (this.writableFinished) {
  //     if (!this.readable && this.closed) {
  //       this.destroy();
  //       return;
  //     }
  //
  //     // We've submitted a response from our server session, have not attempted
  //     // to process any incoming data, and have no trailers. This means we can
  //     // attempt to gracefully close the session.
  //     const state = this[kState];
  //     if (this.headersSent &&
  //     this[kSession] &&
  //       this[kSession][kType] === NGHTTP2_SESSION_SERVER &&
  //       !(state.flags & STREAM_FLAGS_HAS_TRAILERS) &&
  //       !state.didRead &&
  //       this.readableFlowing === null) {
  //       // By using setImmediate we allow pushStreams to make it through
  //       // before the stream is officially closed. This prevents a bug
  //       // in most browsers where those pushStreams would be rejected.
  //       setImmediate(callStreamClose, this);
  //     }
  //   }
  // }
}

function mapToHeaders(mapping) {
  const headers = {};
  for (const key in mapping) {
    if (mapping.hasOwnProperty(key)) {
      // 通常 HTTP 头部字段是大小写不敏感的，但传统上它们的格式是 "camel-Case"。
      // 这里我们将 key 转换为 "camelCase" 格式。
      const headerKey = key.replace(/_([a-z])/g, (match, letter) => letter.toUpperCase());
      headers[headerKey] = mapping[key];
    }
  }
  return headers;
}

const HTTP2_HEADER_SCHEME = ':scheme'
const HTTP2_HEADER_METHOD = ':method'
const HTTP2_HEADER_AUTHORITY = ':authority'
const HTTP2_HEADER_PATH = ':path'
const HTTP2_METHOD_HEAD = 'HEAD'
const HTTP2_HEADER_STATUS = ':status'

export class ServerHttp2Stream extends Http2Stream {
  constructor(session, handle, id, options, headers) {
    super(session, options);
    handle.owner = this;
    this[kInit](id, handle);
    this[kProtocol] = headers[HTTP2_HEADER_SCHEME];
    this[kAuthority] = getAuthority(headers);
  }
  resume() {};
  write(buf?: string) {};

  pause() {};

  on(event: 'response',
    listener: (headers: incomingHttpHeaders & IncomingHttpStatusHeader, flags: number) => void,
  ): void;
  on(event: 'data', callback: (data: Buffer) => void) : void;
  on(event: 'error', callback: (err: Error) => void): void;
  on(event: 'trailers', callback: (headers: IncomingHttpHeaders) => void): void;
  on(event: 'end', callback: () => void): void;
  on(event: 'close', callback: () => void): void;
  on(event: 'remoteSettings', callback: (settings: Settings) => void): void;
  on(event: string, callback: (...args: any[])=>void) {

  }
  once(event: 'close', callback: () => void): void;
  once(event: 'close', callback: (session: ServerHttp2Session) => void ): void;
  once(event: 'wantTrailers', callback: ()=>void): void;
  once(event: 'once', callback: (errorCode: number, lastStreamID: number, opaqueData?: Buffer) => void): void;
  once(event: 'goaway', callback: (errorCode: number, lastStreamID: number, opaqueData?: Buffer) => void): void;
  once(event: 'error', callback: (error) => void): void;
  once(event: string, callback: (...args: any[])=>void) {

  }
  end() {}
  // True if the remote peer accepts push streams
  get pushAllowed() {
    return !this.destroyed &&
      !this.closed &&
      !this.session.closed &&
      !this.session.destroyed &&
    this[kSession].remoteSettings.enablePush;
  }
  destroyed: boolean = false;
  // Create a push stream, call the given callback with the created
  // Http2Stream for the push stream.
  pushStream(headers, options, callback) {
    if (!this.pushAllowed) {
      throw new Error('ERR_HTTP2_PUSH_DISABLED');
    }

    if (this[kID] % 2 === 0) {
      throw new Error('ERR_HTTP2_NESTED_PUSH');
    }


    const session = this[kSession];

    // debugStreamObj(this, 'initiating push stream');

    this[kUpdateTimer]();

    if (typeof options === 'function') {
      callback = options;
      options = undefined;
    }

    validateFunction(callback, 'callback');

    // assertIsObject(options, 'options');
    options = { ...options };
    options.endStream = !!options.endStream;

    // assertIsObject(headers, 'headers');
    // headers = ObjectAssign({ __proto__: null }, headers);

    if (headers[HTTP2_HEADER_METHOD] === undefined)
      headers[HTTP2_HEADER_METHOD] = HTTP2_METHOD_GET;
    if (getAuthority(headers) === undefined)
      headers[HTTP2_HEADER_AUTHORITY] = this[kAuthority];
    if (headers[HTTP2_HEADER_SCHEME] === undefined)
      headers[HTTP2_HEADER_SCHEME] = this[kProtocol];
    if (headers[HTTP2_HEADER_PATH] === undefined)
      headers[HTTP2_HEADER_PATH] = '/';

    let headRequest = false;
    if (headers[HTTP2_HEADER_METHOD] === HTTP2_METHOD_HEAD)
      headRequest = options.endStream = true;

    const headersList = mapToHeaders(headers);

    const streamOptions = options.endStream ? STREAM_OPTION_EMPTY_PAYLOAD : 0;

    const ret = this[kHandle].pushPromise(headersList, streamOptions);
    let err;
    // if (typeof ret === 'number') {
    //   switch (ret) {
    //     case NGHTTP2_ERR_STREAM_ID_NOT_AVAILABLE:
    //       err = new Error('ERR_HTTP2_OUT_OF_STREAMS');
    //       break;
    //     case NGHTTP2_ERR_STREAM_CLOSED:
    //       err = new ERR_HTTP2_INVALID_STREAM();
    //       break;
    //     default:
    //       err = new NghttpError(ret);
    //       break;
    //   }
    //   process.nextTick(callback, err);
    //   return;
    // }

    const id = ret.id();
    const stream = new ServerHttp2Stream(session, ret, id, options, headers);
    stream[kSentHeaders] = headers;

    stream.push(null);

    if (options.endStream)
      stream.end();

    if (headRequest)
      stream[kState].flags |= STREAM_FLAGS_HEAD_REQUEST;

    // process.nextTick(callback, null, stream, headers, 0);
  }

  // Initiate a response on this Http2Stream
  respond(headers, options) {
    if (this.destroyed || this.closed)
      throw new Error('ERR_HTTP2_INVALID_STREAM');
    if (this.headersSent)
      throw new Error('ERR_HTTP2_HEADERS_SENT');

    const state = this[kState];

    // assertIsObject(options, 'options');
    options = { ...options };

    // debugStreamObj(this, 'initiating response');
    this[kUpdateTimer]();

    options.endStream = !!options.endStream;

    let streamOptions = 0;
    if (options.endStream)
      streamOptions |= STREAM_OPTION_EMPTY_PAYLOAD;

    if (options.waitForTrailers) {
      streamOptions |= STREAM_OPTION_GET_TRAILERS;
      state.flags |= STREAM_FLAGS_HAS_TRAILERS;
    }

    // headers = processHeaders(headers, options);
    // const headersList = mapToHeaders(headers, assertValidPseudoHeaderResponse);
    // this[kSentHeaders] = headers;

    state.flags |= STREAM_FLAGS_HEADERS_SENT;

    // Close the writable side if the endStream option is set or status
    // is one of known codes with no payload, or it's a head request
    const statusCode = headers[HTTP2_HEADER_STATUS] | 0;
    if (!!options.endStream ||
      statusCode === expectedStatusCodes.HTTP_STATUS_NO_CONTENT ||
      statusCode === expectedStatusCodes.HTTP_STATUS_RESET_CONTENT ||
      statusCode === expectedStatusCodes.HTTP_STATUS_NOT_MODIFIED ||
      this.headRequest === true) {
      options.endStream = true;
      this.end();
    }

    // const ret = this[kHandle].respond(headersList, streamOptions);
    // if (ret < 0)
    //   this.destroy(new NghttpError(ret));
  }

  // Initiate a response using an open FD. Note that there are fewer
  // protections with this approach. For one, the fd is not validated by
  // default. In respondWithFile, the file is checked to make sure it is a
  // regular file, here the fd is passed directly. If the underlying
  // mechanism is not able to read from the fd, then the stream will be
  // reset with an error code.
  respondWithFD(fd, headers, options) {
    if (this.destroyed || this.closed)
      throw new Error('ERR_HTTP2_INVALID_STREAM');
    if (this.headersSent)
      throw new Error('ERR_HTTP2_HEADERS_SENT');

    const session = this[kSession];

    // assertIsObject(options, 'options');
    options = { ...options };

    if (options.offset !== undefined && typeof options.offset !== 'number')
      throw new Error(`ERR_INVALID_ARG_VALUE('options.offset', options.offset)`);

    if (options.length !== undefined && typeof options.length !== 'number')
      throw new Error(`ERR_INVALID_ARG_VALUE('options.length', options.length)`);

    if (options.statCheck !== undefined &&
      typeof options.statCheck !== 'function') {
      throw new Error(`ERR_INVALID_ARG_VALUE('options.statCheck', options.statCheck)`);
    }

    let streamOptions = 0;
    if (options.waitForTrailers) {
      streamOptions |= STREAM_OPTION_GET_TRAILERS;
      this[kState].flags |= STREAM_FLAGS_HAS_TRAILERS;
    }

    // if (fd instanceof fsPromisesInternal.FileHandle)
    //   fd = fd.fd;
    // else if (typeof fd !== 'number')
    //   throw new ERR_INVALID_ARG_TYPE('fd', ['number', 'FileHandle'], fd);
    //
    // debugStreamObj(this, 'initiating response from fd');
    // this[kUpdateTimer]();
    // this.ownsFd = false;
    //
    // headers = processHeaders(headers, options);
    // const statusCode = headers[HTTP2_HEADER_STATUS] |= 0;
    // // Payload/DATA frames are not permitted in these cases
    // if (statusCode === HTTP_STATUS_NO_CONTENT ||
    //   statusCode === HTTP_STATUS_RESET_CONTENT ||
    //   statusCode === HTTP_STATUS_NOT_MODIFIED ||
    // this.headRequest) {
    //   throw new ERR_HTTP2_PAYLOAD_FORBIDDEN(statusCode);
    // }
    //
    // if (options.statCheck !== undefined) {
    //   fs.fstat(fd,
    //     doSendFD.bind(this, session, options, fd, headers, streamOptions));
    //   return;
    // }
    //
    // processRespondWithFD(this, fd, headers,
    //   options.offset,
    //   options.length,
    //   streamOptions);
  }

  // Initiate a file response on this Http2Stream. The path is passed to
  // fs.open() to acquire the fd with mode 'r', then the fd is passed to
  // fs.fstat(). Assuming fstat is successful, a check is made to ensure
  // that the file is a regular file, then options.statCheck is called,
  // giving the user an opportunity to verify the details and set additional
  // headers. If statCheck returns false, the operation is aborted and no
  // file details are sent.
  respondWithFile(path, headers, options) {
    // if (this.destroyed || this.closed)
    //   throw new ERR_HTTP2_INVALID_STREAM();
    // if (this.headersSent)
    //   throw new ERR_HTTP2_HEADERS_SENT();
    //
    // assertIsObject(options, 'options');
    // options = { ...options };
    //
    // if (options.offset !== undefined && typeof options.offset !== 'number')
    //   throw new ERR_INVALID_ARG_VALUE('options.offset', options.offset);
    //
    // if (options.length !== undefined && typeof options.length !== 'number')
    //   throw new ERR_INVALID_ARG_VALUE('options.length', options.length);
    //
    // if (options.statCheck !== undefined &&
    //   typeof options.statCheck !== 'function') {
    //   throw new ERR_INVALID_ARG_VALUE('options.statCheck', options.statCheck);
    // }
    //
    // let streamOptions = 0;
    // if (options.waitForTrailers) {
    //   streamOptions |= STREAM_OPTION_GET_TRAILERS;
    //   this[kState].flags |= STREAM_FLAGS_HAS_TRAILERS;
    // }
    //
    // const session = this[kSession];
    // debugStreamObj(this, 'initiating response from file');
    // this[kUpdateTimer]();
    // this.ownsFd = true;
    //
    // headers = processHeaders(headers, options);
    // const statusCode = headers[HTTP2_HEADER_STATUS] |= 0;
    // // Payload/DATA frames are not permitted in these cases
    // if (statusCode === HTTP_STATUS_NO_CONTENT ||
    //   statusCode === HTTP_STATUS_RESET_CONTENT ||
    //   statusCode === HTTP_STATUS_NOT_MODIFIED ||
    // this.headRequest) {
    //   throw new ERR_HTTP2_PAYLOAD_FORBIDDEN(statusCode);
    // }
    //
    // fs.open(path, 'r',
    //   afterOpen.bind(this, session, options, headers, streamOptions));
  }

  // Sends a block of informational headers. In theory, the HTTP/2 spec
  // allows sending a HEADER block at any time during a streams lifecycle,
  // but the HTTP request/response semantics defined in HTTP/2 places limits
  // such that HEADERS may only be sent *before* or *after* DATA frames.
  // If the block of headers being sent includes a status code, it MUST be
  // a 1xx informational code and it MUST be sent before the request/response
  // headers are sent, or an error will be thrown.
  additionalHeaders(headers) {
    // if (this.destroyed || this.closed)
    //   throw new ERR_HTTP2_INVALID_STREAM();
    // if (this.headersSent)
    //   throw new ERR_HTTP2_HEADERS_AFTER_RESPOND();
    //
    // assertIsObject(headers, 'headers');
    // headers = ObjectAssign({ __proto__: null }, headers);
    //
    // debugStreamObj(this, 'sending additional headers');
    //
    // if (headers[HTTP2_HEADER_STATUS] != null) {
    //   const statusCode = headers[HTTP2_HEADER_STATUS] |= 0;
    //   if (statusCode === HTTP_STATUS_SWITCHING_PROTOCOLS)
    //     throw new ERR_HTTP2_STATUS_101();
    //   if (statusCode < 100 || statusCode >= 200) {
    //     throw new ERR_HTTP2_INVALID_INFO_STATUS(headers[HTTP2_HEADER_STATUS]);
    //   }
    // }
    //
    // this[kUpdateTimer]();
    //
    // const headersList = mapToHeaders(headers, assertValidPseudoHeaderResponse);
    // if (!this[kInfoHeaders])
    //   this[kInfoHeaders] = [headers];
    // else
    //   this[kInfoHeaders].push(headers);
    //
    // const ret = this[kHandle].info(headersList);
    // if (ret < 0)
    //   this.destroy(new NghttpError(ret));
  }
}

export class ClientHttp2Stream extends Http2Stream {
  constructor(session, handle, id, options) {
    super(session, options);
    this[kState].flags |= STREAM_FLAGS_HEADERS_SENT;
    if (id !== undefined)
      this[kInit](id, handle);
    // this.on('headers', handleHeaderContinue);
  }
  on(event: 'response',
    listener: (headers: incomingHttpHeaders & IncomingHttpStatusHeader, flags: number) => void,
  ): void;
  on(event: 'data', callback: (data: Buffer) => void) : void;
  on(event: 'error', callback: (err: Error) => void): void;
  on(event: 'trailers', callback: (headers: IncomingHttpHeaders) => void): void;
  on(event: 'end', callback: () => void): void;
  on(event: 'close', callback: () => void): void;
  on(event: 'remoteSettings', callback: (settings: Settings) => void): void;
  on(event: string, callback: (...args: any[])=>void) {

  }
  once(event: 'close', callback: () => void): void;
  once(event: 'once', callback: (errorCode: number, lastStreamID: number, opaqueData?: Buffer) => void): void;
  once(event: 'goaway', callback: (errorCode: number, lastStreamID: number, opaqueData?: Buffer) => void): void;
  once(event: 'error', callback: (error) => void): void;
  once(event: string, callback: (...args: any[])=>void) {

  }
  write(buf: string) {};
  resume(){};
  pause(){};
  destroyed(){};
  end(){};
  close(code: number): void {
    console.log(`Closing with code: ${code}`);
    // 实现关闭逻辑，可能包含错误处理等
  }
}


export class ClientHttp2Session extends Http2Session {
  constructor(options, socket) {
    super(expectedNGConstants.NGHTTP2_SESSION_CLIENT, options, socket);
    this[kPendingRequestCalls] = null;
  }
  on(event: 'response',
    listener: (headers: incomingHttpHeaders & IncomingHttpStatusHeader, flags: number) => void,
  ): void;
  on(event: 'data', callback: (data: Buffer) => void) : void;
  on(event: 'error', callback: (err: Error) => void): void;
  on(event: 'trailers', callback: (headers: IncomingHttpHeaders) => void): void;
  on(event: 'end', callback: () => void): void;
  on(event: 'close', callback: () => void): void;
  on(event: 'remoteSettings', callback: (settings: Settings) => void): void;
  on(event: 'localSettings', callback: (settings: Settings) => void): void;
  on(event: string, callback: (...args: any[])=>void) {

  }
  once(event: 'close', callback: () => void): void;
  once(event: 'connect', callback: () => void): void;
  once(event: 'once', callback: (errorCode: number, lastStreamID: number, opaqueData?: Buffer) => void): void;
  once(event: 'goaway', callback: (errorCode: number, lastStreamID: number, opaqueData?: Buffer) => void): void;
  once(event: 'error', callback: (error) => void): void;
  once(event: string, callback: (...args: any[])=>void) {

  }

  removeAllListeners() {};
  close() {};

  // Submits a new HTTP2 request to the connected peer. Returns the
  // associated Http2Stream instance.
  request(headers?, options?) {
    debugSessionObj(this, 'initiating request');

    if (this.destroyed)
      throw new Error('expectedNGConstants.ERR_HTTP2_INVALID_SESSION');

    if (this.closed)
      throw new Error('ERR_HTTP2_GOAWAY_SESSION');

    this[kUpdateTimer]();

    // if (headers !== null && headers !== undefined) {
    //   const keys = ObjectKeys(headers);
    //   for (let i = 0; i < keys.length; i++) {
    //     const header = keys[i];
    //     if (header[0] === ':') {
    //       assertValidPseudoHeader(header);
    //     } else if (header && !checkIsHttpToken(header))
    //       this.destroy(new ERR_INVALID_HTTP_TOKEN('Header name', header));
    //   }
    // }

    // assertIsObject(headers, 'headers');
    // assertIsObject(options, 'options');

    // headers = ObjectAssign({ __proto__: null }, headers);
    options = { ...options };

    if (headers[expectedHeaderNames.HTTP2_HEADER_METHOD] === undefined)
      headers[expectedHeaderNames.HTTP2_HEADER_METHOD] = HTTP2_METHOD_GET;

    const connect = headers[expectedHeaderNames.HTTP2_HEADER_METHOD] === HTTP2_METHOD_CONNECT;

    if (!connect || headers[expectedHeaderNames.HTTP2_HEADER_PROTOCOL] !== undefined) {
      if (getAuthority(headers) === undefined)
        headers[expectedHeaderNames.HTTP2_HEADER_AUTHORITY] = this[kAuthority];
      if (headers[expectedHeaderNames.HTTP2_HEADER_SCHEME] === undefined)
        headers[expectedHeaderNames.HTTP2_HEADER_SCHEME] = this[kProtocol].slice(0, -1);
      if (headers[expectedHeaderNames.HTTP2_HEADER_PATH] === undefined)
        headers[expectedHeaderNames.HTTP2_HEADER_PATH] = '/';
    } else {
      if (headers[expectedHeaderNames.HTTP2_HEADER_AUTHORITY] === undefined) {
        hilog.error(0x8691, 'ohos_grpc_node', 'ERR_HTTP2_CONNECT_PATH')
        throw new Error('ERR_HTTP2_CONNECT_AUTHORITY');
      }
      if (headers[expectedHeaderNames.HTTP2_HEADER_SCHEME] !== undefined) {
        hilog.error(0x8691, 'ohos_grpc_node', 'ERR_HTTP2_CONNECT_PATH')
        throw new Error('ERR_HTTP2_CONNECT_SCHEME');
      }
      if (headers[expectedHeaderNames.HTTP2_HEADER_PATH] !== undefined) {
        hilog.error(0x8691, 'ohos_grpc_node', 'ERR_HTTP2_CONNECT_PATH')
        throw new Error('ERR_HTTP2_CONNECT_PATH');
      }

    }

    setAndValidatePriorityOptions(options);

    if (options.endStream === undefined) {
      // For some methods, we know that a payload is meaningless, so end the
      // stream by default if the user has not specifically indicated a
      // preference.
      options.endStream = isPayloadMeaningless(headers[expectedHeaderNames.HTTP2_HEADER_METHOD]);
    } else {
      validateBoolean(options.endStream, 'options.endStream');
    }

    // const headersList = mapToHeaders(headers);

    // eslint-disable-next-line no-use-before-define
    const stream = new ClientHttp2Stream(this, undefined, undefined, {});
    stream[kSentHeaders] = headers;
    stream[kOrigin] = `${headers[expectedHeaderNames.HTTP2_HEADER_SCHEME]}://` +
      `${getAuthority(headers)}`;
    // const reqAsync = new AsyncResource('PendingRequest');
    // stream[kRequestAsyncResource] = reqAsync;

    // Close the writable side of the stream if options.endStream is set.
    if (options.endStream)
      stream.end();

    if (options.waitForTrailers)
      stream[kState].flags |= STREAM_FLAGS_HAS_TRAILERS;

    const { signal } = options;
    if (signal) {
      // validateAbortSignal(signal, 'options.signal');
      const aborter = () => {
        hilog.error(0x8691, 'ohos_grpc_node', 'abortError');
        // stream.destroy(new AbortError(undefined, { cause: signal.reason }));
      };
      if (signal.aborted) {
        aborter();
      } else {
        hilog.error(0x8691, 'ohos_grpc_node', 'addAbortListener');
        // const disposable = addAbortListener(signal, aborter);
        // stream.once('close', disposable[SymbolDispose]);
      }
    }

    // const onConnect = reqAsync.bind(requestOnConnect.bind(stream, headersList, options));
    // if (this.connecting) {
    //   if (this[kPendingRequestCalls] !== null) {
    //     this[kPendingRequestCalls].push(onConnect);
    //   } else {
    //     this[kPendingRequestCalls] = [onConnect];
    //     // this.once('connect', () => {
    //     //   this[kPendingRequestCalls].forEach((f) => f());
    //     //   this[kPendingRequestCalls] = null;
    //     // });
    //   }
    // } else {
    //   onConnect();
    // }
    return stream;
  }
}

export class http2 {
  static connect(
    authority: string | URL,
    options?: ClientSessionOptions | SecureClientSessionOptions,
    listener?: (session: ClientHttp2Session, socket: Socket) => void
  ): ClientHttp2Session {
    let mockSession: ClientHttp2Session;
    // const mockSocket: Socket | TLSSocket = {
    //   destroy: () => {
    //     console.log('Mock socket destroyed');
    //   },
    // } as any;
    //
    // if (listener) {
    //   listener(mockSession, mockSocket);
    // }
    if (typeof options === 'function') {
      listener = options;
      options = undefined;
    }
    if (options.remoteCustomSettings) {
      options.remoteCustomSettings = [ ...options.remoteCustomSettings ];
      if (options.remoteCustomSettings.length > MAX_ADDITIONAL_SETTINGS) {
        hilog.error(0x8691, 'ohos_grpc_node', `http2 ERR_CODE.ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS`)
        throw new Error(`http2 ERR_CODE.ERR_HTTP2_TOO_MANY_CUSTOM_SETTINGS`);
      }
    }

    if (typeof authority === 'string') {
      authority = new Url.URL(authority);
    }

    const protocol = authority.protocol || options.protocol || 'https:';
    const port = '' + (authority.port !== '' ?
    authority.port : (authority.protocol === 'http:' ? 80 : 443));
    let host = 'localhost';

    if (authority.hostname) {
      host = authority.hostname;

      if (host[0] === '[')
        host = host.slice(1, -1);
    } else if (authority.host) {
      host = authority.host;
    }

    let mysocket;
    hilog.info(0x8691, 'ohos_grpc_node', `option: ${JSON.stringify(options)}`);
    // if (typeof options.createConnection === 'function') {
    //   hilog.info(0x8691, 'ohos_grpc_node', `options.createConnection`);
    //   socket = options.createConnection(authority, options);
    // } else
    {
      hilog.info(0x8691, 'ohos_grpc_node', `switch.protocol: ${protocol}`);
      switch (protocol) {
        case 'http:':
          {
            // socket = net.connect({ port, host, ...options });
            let tcp: socket.TCPSocket = socket.constructTCPSocketInstance();
            let tcpconnectoptions: socket.TCPConnectOptions = {
              address: {
                address: host,
                port: parseInt(port),
              },
              timeout: 6000
            }
            hilog.info(0x8691, 'ohos_grpc_node', `tcp.connect: ${JSON.stringify(tcpconnectoptions)}`);
            tcp.connect(tcpconnectoptions, (err: BusinessError) => {
              if (err) {
                hilog.error(0x8691, 'ohos_grpc_node', `connect fail: ${JSON.stringify(err)}`);
                return;
              }
              hilog.error(0x8691, 'ohos_grpc_node', 'connect success');
            })
            mysocket = tcp;
          }
          break;
        case 'https:':
          {
            // socket = tls.connect(port, host, initializeTLSOptions(options, host));
            let tlsTwoWay: socket.TLSSocket = socket.constructTLSSocketInstance();  // Two way authentication
            let bindAddr: socket.NetAddress = {
              address: '0.0.0.0',
            }
            tlsTwoWay.bind(bindAddr, (err: BusinessError) => {
              if (err) {
                console.log('bind fail');
                return;
              }
              console.log('bind success');
            });
            let tlsConnectOptions: socket.TLSConnectOptions = {
              address: {
                address: host,
                port: parseInt(port)
              },
              secureOptions: {
                key: "xxxx",
                cert: "xxxx",
                ca: ["xxxx"],
                password: "xxxx",
                protocols: socket.Protocol.TLSv12,
                useRemoteCipherPrefer: true,
                signatureAlgorithms: "rsa_pss_rsae_sha256:ECDSA+SHA256",
                cipherSuite: "AES256-SHA256"
              },
              ALPNProtocols: ["spdy/1", "http/1.1"]
            }

            tlsTwoWay.connect(tlsConnectOptions, (err: BusinessError) => {
              console.error("connect callback error" + err);
            });
            mysocket = tlsTwoWay;
          }

          break;
        default:
          throw new Error(`ERR_HTTP2_UNSUPPORTED_PROTOCOL(${protocol})`);
      }
    }
    hilog.info(0x8691, 'ohos_grpc_node',
      `create http2.connect: ${JSON.stringify(authority)}, ${JSON.stringify(options)}`);

    const session = new ClientHttp2Session(options, mysocket);

    session[kAuthority] = `${options.servername || host}:${port}`;
    session[kProtocol] = protocol;

    // if (typeof listener === 'function') {
    //   session.once('connect', listener);
    // }

    return session;
  }
}
